Example #1
0
    def __init__(self, *args, **kwargs):
        my_kwargs = self.pop_kwargs(kwargs,
            *[prop.label for prop in RawPatternPhase.Meta.get_local_persistent_properties()]
        )
        super(RawPatternPhase, self).__init__(*args, **kwargs)
        kwargs = my_kwargs

        with self.data_changed.hold():
            self.raw_pattern = PyXRDLine(
                data=self.get_kwarg(kwargs, None, "raw_pattern"),
                parent=self
            )
            self.display_color = self.get_kwarg(kwargs, choice(self.line_colors), "display_color")
            self.inherit_display_color = self.get_kwarg(kwargs, False, "inherit_display_color")
Example #2
0
    def __init__(self, *args, **kwargs):
        """
            Valid keyword arguments for a Specimen are:
                name: the name of the specimen
                sample_name: the sample name of the specimen
                calculated_pattern: the calculated pattern
                experimental_pattern: the experimental pattern
                exclusion_ranges: the exclusion ranges XYListStore
                goniometer: the goniometer used for recording data
                markers: the specimen's markers
                display_vshift: the patterns vertical shift from its default position
                display_vscale: the patterns vertical scale (default is 1.0)
                display_calculated: whether or not to show the calculated pattern
                display_experimental: whether or not to show the experimental pattern
                display_residuals: whether or not to show the residuals
                display_derivatives: whether or not to show the 1st derivative patterns
                display_phases: whether or not to show the separate phase patterns
                display_stats_in_lbl: whether or not to display the Rp values 
                 in the pattern label
        """

        my_kwargs = self.pop_kwargs(
            kwargs, "data_name", "data_sample", "data_sample_length",
            "data_calculated_pattern", "data_experimental_pattern",
            "calc_color", "calc_lw", "inherit_calc_color", "inherit_calc_lw",
            "exp_color", "exp_lw", "inherit_exp_color", "inherit_exp_lw",
            "project_goniometer", "data_markers", "bg_shift", "abs_scale",
            "exp_cap_value", "sample_length", "absorption", "sample_z_dev", *[
                prop.label
                for prop in Specimen.Meta.get_local_persistent_properties()
            ])
        super(Specimen, self).__init__(*args, **kwargs)
        kwargs = my_kwargs

        self._data_object = SpecimenData()

        with self.visuals_changed.hold_and_emit():
            with self.data_changed.hold_and_emit():
                self.name = self.get_kwarg(kwargs, "", "name", "data_name")
                sample_name = self.get_kwarg(kwargs, "", "sample_name",
                                             "data_sample")
                if isinstance(sample_name, bytes):
                    sample_name = sample_name.decode("utf-8", "ignore")
                self.sample_name = sample_name

                calc_pattern_old_kwargs = {}
                for kw in ("calc_color", "calc_lw", "inherit_calc_color",
                           "inherit_calc_lw"):
                    if kw in kwargs:
                        calc_pattern_old_kwargs[kw.replace(
                            "calc_", "")] = kwargs.pop(kw)
                self.calculated_pattern = self.parse_init_arg(
                    self.get_kwarg(kwargs, None, "calculated_pattern",
                                   "data_calculated_pattern"),
                    CalculatedLine,
                    child=True,
                    default_is_class=True,
                    label="Calculated Profile",
                    parent=self,
                    **calc_pattern_old_kwargs)

                exp_pattern_old_kwargs = {}
                for kw in ("exp_color", "exp_lw", "inherit_exp_color",
                           "inherit_exp_lw"):
                    if kw in kwargs:
                        exp_pattern_old_kwargs[kw.replace("exp_",
                                                          "")] = kwargs.pop(kw)
                self.experimental_pattern = self.parse_init_arg(
                    self.get_kwarg(kwargs, None, "experimental_pattern",
                                   "data_experimental_pattern"),
                    ExperimentalLine,
                    child=True,
                    default_is_class=True,
                    label="Experimental Profile",
                    parent=self,
                    **exp_pattern_old_kwargs)

                self.exclusion_ranges = PyXRDLine(data=self.get_kwarg(
                    kwargs, None, "exclusion_ranges"),
                                                  parent=self)

                # Extract old kwargs if they are there:
                gonio_kwargs = {}
                sample_length = self.get_kwarg(kwargs, None, "sample_length",
                                               "data_sample_length")
                if sample_length is not None:
                    gonio_kwargs["sample_length"] = float(sample_length)
                absorption = self.get_kwarg(kwargs, None, "absorption")
                if absorption is not None:  # assuming a surface density of at least 20 mg/cm²:
                    gonio_kwargs["absorption"] = float(absorption) / 0.02

                # Initialize goniometer (with optional old kwargs):
                self.goniometer = self.parse_init_arg(self.get_kwarg(
                    kwargs, None, "goniometer", "project_goniometer"),
                                                      Goniometer,
                                                      child=True,
                                                      default_is_class=True,
                                                      parent=self,
                                                      **gonio_kwargs)

                self.markers = self.get_list(kwargs,
                                             None,
                                             "markers",
                                             "data_markers",
                                             parent=self)
                for marker in self.markers:
                    self.observe_model(marker)
                self._specimens_observer = ListObserver(
                    self.on_marker_inserted,
                    self.on_marker_removed,
                    prop_name="markers",
                    model=self)

                self.display_vshift = float(
                    self.get_kwarg(kwargs, 0.0, "display_vshift"))
                self.display_vscale = float(
                    self.get_kwarg(kwargs, 1.0, "display_vscale"))
                self.display_calculated = bool(
                    self.get_kwarg(kwargs, True, "display_calculated"))
                self.display_experimental = bool(
                    self.get_kwarg(kwargs, True, "display_experimental"))
                self.display_residuals = bool(
                    self.get_kwarg(kwargs, True, "display_residuals"))
                self.display_residual_scale = float(
                    self.get_kwarg(kwargs, 1.0, "display_residual_scale"))
                self.display_derivatives = bool(
                    self.get_kwarg(kwargs, False, "display_derivatives"))
                self.display_phases = bool(
                    self.get_kwarg(kwargs, False, "display_phases"))
                self.display_stats_in_lbl = bool(
                    self.get_kwarg(kwargs, True, "display_stats_in_lbl"))

                self.statistics = Statistics(parent=self)

                pass  # end of with
            pass  # end of with
        pass  # end of __init__
Example #3
0
class Specimen(DataModel, Storable):
    # MODEL INTEL:
    class Meta(DataModel.Meta):
        store_id = "Specimen"

        export_filters = xrd_parsers.get_export_file_filters()
        excl_filters = exc_parsers.get_import_file_filters()

    _data_object = None

    @property
    def data_object(self):
        self._data_object.goniometer = self.goniometer.data_object
        self._data_object.range_theta = self.__get_range_theta()
        self._data_object.selected_range = self.get_exclusion_selector()
        self._data_object.z_list = self.get_z_list()
        try:
            self._data_object.observed_intensity = np.copy(
                self.experimental_pattern.data_y)
        except IndexError:
            self._data_object.observed_intensity = np.array([], dtype=float)
        return self._data_object

    def get_z_list(self):
        return list(self.experimental_pattern.z_data)

    project = property(DataModel.parent.fget, DataModel.parent.fset)

    # PROPERTIES:

    #: The sample name
    sample_name = StringProperty(default="",
                                 text="Sample",
                                 visible=True,
                                 persistent=True,
                                 tabular=True,
                                 signal_name="visuals_changed",
                                 mix_with=(SignalMixin, ))

    #: The sample name
    name = StringProperty(default="",
                          text="Name",
                          visible=True,
                          persistent=True,
                          tabular=True,
                          signal_name="visuals_changed",
                          mix_with=(SignalMixin, ))

    @StringProperty(default="",
                    text="Label",
                    visible=False,
                    persistent=False,
                    tabular=True,
                    mix_with=(ReadOnlyMixin, ))
    def label(self):
        if self.display_stats_in_lbl and (self.project is not None and
                                          self.project.layout_mode == "FULL"):
            label = self.sample_name
            label += "\nRp = %.1f%%" % not_none(self.statistics.Rp, 0.0)
            label += "\nRwp = %.1f%%" % not_none(self.statistics.Rwp, 0.0)
            return label
        else:
            return self.sample_name

    display_calculated = BoolProperty(default=True,
                                      text="Display calculated diffractogram",
                                      visible=True,
                                      persistent=True,
                                      tabular=True,
                                      signal_name="visuals_changed",
                                      mix_with=(SignalMixin, ))

    display_experimental = BoolProperty(
        default=True,
        text="Display experimental diffractogram",
        visible=True,
        persistent=True,
        tabular=True,
        signal_name="visuals_changed",
        mix_with=(SignalMixin, ))

    display_vshift = FloatProperty(default=0.0,
                                   text="Vertical shift of the plot",
                                   visible=True,
                                   persistent=True,
                                   tabular=True,
                                   signal_name="visuals_changed",
                                   widget_type="spin",
                                   mix_with=(SignalMixin, ))

    display_vscale = FloatProperty(default=0.0,
                                   text="Vertical scale of the plot",
                                   visible=True,
                                   persistent=True,
                                   tabular=True,
                                   signal_name="visuals_changed",
                                   widget_type="spin",
                                   mix_with=(SignalMixin, ))

    display_phases = BoolProperty(default=True,
                                  text="Display phases seperately",
                                  visible=True,
                                  persistent=True,
                                  tabular=True,
                                  signal_name="visuals_changed",
                                  mix_with=(SignalMixin, ))

    display_stats_in_lbl = BoolProperty(default=True,
                                        text="Display Rp in label",
                                        visible=True,
                                        persistent=True,
                                        tabular=True,
                                        signal_name="visuals_changed",
                                        mix_with=(SignalMixin, ))

    display_residuals = BoolProperty(default=True,
                                     text="Display residual patterns",
                                     visible=True,
                                     persistent=True,
                                     tabular=True,
                                     signal_name="visuals_changed",
                                     mix_with=(SignalMixin, ))

    display_residual_scale = FloatProperty(default=1.0,
                                           text="Residual pattern scale",
                                           minimum=0.0,
                                           visible=True,
                                           persistent=True,
                                           tabular=True,
                                           signal_name="visuals_changed",
                                           widget_type="spin",
                                           mix_with=(SignalMixin, ))

    display_derivatives = BoolProperty(default=False,
                                       text="Display derivative patterns",
                                       visible=True,
                                       persistent=True,
                                       tabular=True,
                                       signal_name="visuals_changed",
                                       mix_with=(SignalMixin, ))

    #: A :class:`~pyxrd.generic.models.lines.CalculatedLine` instance
    calculated_pattern = LabeledProperty(default=None,
                                         text="Calculated diffractogram",
                                         visible=True,
                                         persistent=True,
                                         tabular=True,
                                         signal_name="data_changed",
                                         widget_type="xy_list_view",
                                         mix_with=(
                                             SignalMixin,
                                             ObserveMixin,
                                         ))

    #: A :class:`~pyxrd.generic.models.lines.ExperimentalLine` instance
    experimental_pattern = LabeledProperty(default=None,
                                           text="Experimental diffractogram",
                                           visible=True,
                                           persistent=True,
                                           tabular=True,
                                           signal_name="data_changed",
                                           widget_type="xy_list_view",
                                           mix_with=(
                                               SignalMixin,
                                               ObserveMixin,
                                           ))

    #: A list of 2-theta ranges to exclude for the calculation of the Rp factor
    exclusion_ranges = LabeledProperty(default=None,
                                       text="Excluded ranges",
                                       visible=True,
                                       persistent=True,
                                       tabular=True,
                                       signal_name="data_changed",
                                       widget_type="xy_list_view",
                                       mix_with=(SignalMixin, ObserveMixin))

    #: A :class:`~pyxrd.goniometer.models.Goniometer` instance
    goniometer = LabeledProperty(default=None,
                                 text="Goniometer",
                                 visible=True,
                                 persistent=True,
                                 tabular=True,
                                 signal_name="data_changed",
                                 mix_with=(
                                     SignalMixin,
                                     ObserveMixin,
                                 ))

    #: A :class:`~pyxrd.specimen.models.Statistics` instance
    statistics = LabeledProperty(
        default=None,
        text="Markers",
        visible=False,
        persistent=False,
        tabular=True,
    )

    #: A list :class:`~pyxrd.specimen.models.Marker` instances
    markers = ListProperty(default=None,
                           text="Markers",
                           data_type=Marker,
                           visible=False,
                           persistent=True,
                           tabular=True,
                           signal_name="visuals_changed",
                           widget_type="object_list_view",
                           mix_with=(SignalMixin, ))

    @property
    def max_display_y(self):
        """
         The maximum intensity or z-value (display y axis) 
         of the current profile (both calculated and observed)
        """
        _max = 0.0
        if self.experimental_pattern is not None:
            _max = max(_max, np.max(self.experimental_pattern.max_display_y))
        if self.calculated_pattern is not None:
            _max = max(_max, np.max(self.calculated_pattern.max_display_y))
        return _max

    # ------------------------------------------------------------
    #      Initialisation and other internals
    # ------------------------------------------------------------
    def __init__(self, *args, **kwargs):
        """
            Valid keyword arguments for a Specimen are:
                name: the name of the specimen
                sample_name: the sample name of the specimen
                calculated_pattern: the calculated pattern
                experimental_pattern: the experimental pattern
                exclusion_ranges: the exclusion ranges XYListStore
                goniometer: the goniometer used for recording data
                markers: the specimen's markers
                display_vshift: the patterns vertical shift from its default position
                display_vscale: the patterns vertical scale (default is 1.0)
                display_calculated: whether or not to show the calculated pattern
                display_experimental: whether or not to show the experimental pattern
                display_residuals: whether or not to show the residuals
                display_derivatives: whether or not to show the 1st derivative patterns
                display_phases: whether or not to show the separate phase patterns
                display_stats_in_lbl: whether or not to display the Rp values 
                 in the pattern label
        """

        my_kwargs = self.pop_kwargs(
            kwargs, "data_name", "data_sample", "data_sample_length",
            "data_calculated_pattern", "data_experimental_pattern",
            "calc_color", "calc_lw", "inherit_calc_color", "inherit_calc_lw",
            "exp_color", "exp_lw", "inherit_exp_color", "inherit_exp_lw",
            "project_goniometer", "data_markers", "bg_shift", "abs_scale",
            "exp_cap_value", "sample_length", "absorption", "sample_z_dev", *[
                prop.label
                for prop in Specimen.Meta.get_local_persistent_properties()
            ])
        super(Specimen, self).__init__(*args, **kwargs)
        kwargs = my_kwargs

        self._data_object = SpecimenData()

        with self.visuals_changed.hold_and_emit():
            with self.data_changed.hold_and_emit():
                self.name = self.get_kwarg(kwargs, "", "name", "data_name")
                sample_name = self.get_kwarg(kwargs, "", "sample_name",
                                             "data_sample")
                if isinstance(sample_name, bytes):
                    sample_name = sample_name.decode("utf-8", "ignore")
                self.sample_name = sample_name

                calc_pattern_old_kwargs = {}
                for kw in ("calc_color", "calc_lw", "inherit_calc_color",
                           "inherit_calc_lw"):
                    if kw in kwargs:
                        calc_pattern_old_kwargs[kw.replace(
                            "calc_", "")] = kwargs.pop(kw)
                self.calculated_pattern = self.parse_init_arg(
                    self.get_kwarg(kwargs, None, "calculated_pattern",
                                   "data_calculated_pattern"),
                    CalculatedLine,
                    child=True,
                    default_is_class=True,
                    label="Calculated Profile",
                    parent=self,
                    **calc_pattern_old_kwargs)

                exp_pattern_old_kwargs = {}
                for kw in ("exp_color", "exp_lw", "inherit_exp_color",
                           "inherit_exp_lw"):
                    if kw in kwargs:
                        exp_pattern_old_kwargs[kw.replace("exp_",
                                                          "")] = kwargs.pop(kw)
                self.experimental_pattern = self.parse_init_arg(
                    self.get_kwarg(kwargs, None, "experimental_pattern",
                                   "data_experimental_pattern"),
                    ExperimentalLine,
                    child=True,
                    default_is_class=True,
                    label="Experimental Profile",
                    parent=self,
                    **exp_pattern_old_kwargs)

                self.exclusion_ranges = PyXRDLine(data=self.get_kwarg(
                    kwargs, None, "exclusion_ranges"),
                                                  parent=self)

                # Extract old kwargs if they are there:
                gonio_kwargs = {}
                sample_length = self.get_kwarg(kwargs, None, "sample_length",
                                               "data_sample_length")
                if sample_length is not None:
                    gonio_kwargs["sample_length"] = float(sample_length)
                absorption = self.get_kwarg(kwargs, None, "absorption")
                if absorption is not None:  # assuming a surface density of at least 20 mg/cm²:
                    gonio_kwargs["absorption"] = float(absorption) / 0.02

                # Initialize goniometer (with optional old kwargs):
                self.goniometer = self.parse_init_arg(self.get_kwarg(
                    kwargs, None, "goniometer", "project_goniometer"),
                                                      Goniometer,
                                                      child=True,
                                                      default_is_class=True,
                                                      parent=self,
                                                      **gonio_kwargs)

                self.markers = self.get_list(kwargs,
                                             None,
                                             "markers",
                                             "data_markers",
                                             parent=self)
                for marker in self.markers:
                    self.observe_model(marker)
                self._specimens_observer = ListObserver(
                    self.on_marker_inserted,
                    self.on_marker_removed,
                    prop_name="markers",
                    model=self)

                self.display_vshift = float(
                    self.get_kwarg(kwargs, 0.0, "display_vshift"))
                self.display_vscale = float(
                    self.get_kwarg(kwargs, 1.0, "display_vscale"))
                self.display_calculated = bool(
                    self.get_kwarg(kwargs, True, "display_calculated"))
                self.display_experimental = bool(
                    self.get_kwarg(kwargs, True, "display_experimental"))
                self.display_residuals = bool(
                    self.get_kwarg(kwargs, True, "display_residuals"))
                self.display_residual_scale = float(
                    self.get_kwarg(kwargs, 1.0, "display_residual_scale"))
                self.display_derivatives = bool(
                    self.get_kwarg(kwargs, False, "display_derivatives"))
                self.display_phases = bool(
                    self.get_kwarg(kwargs, False, "display_phases"))
                self.display_stats_in_lbl = bool(
                    self.get_kwarg(kwargs, True, "display_stats_in_lbl"))

                self.statistics = Statistics(parent=self)

                pass  # end of with
            pass  # end of with
        pass  # end of __init__

    def __str__(self):
        return "<Specimen %s(%s)>" % (self.name, repr(self))

    # ------------------------------------------------------------
    #      Notifications of observable properties
    # ------------------------------------------------------------
    @DataModel.observe("data_changed", signal=True)
    def notify_data_changed(self, model, prop_name, info):
        if model == self.calculated_pattern:
            self.visuals_changed.emit()  # don't propagate this as data_changed
        else:
            self.data_changed.emit()  # propagate signal

    @DataModel.observe("visuals_changed", signal=True)
    def notify_visuals_changed(self, model, prop_name, info):
        self.visuals_changed.emit()  # propagate signal

    def on_marker_removed(self, item):
        with self.visuals_changed.hold_and_emit():
            self.relieve_model(item)
            item.parent = None

    def on_marker_inserted(self, item):
        with self.visuals_changed.hold_and_emit():
            self.observe_model(item)
            item.parent = self

    # ------------------------------------------------------------
    #      Input/Output stuff
    # ------------------------------------------------------------
    @staticmethod
    def from_experimental_data(filename,
                               parent,
                               parser=xrd_parsers._group_parser,
                               load_as_insitu=False):
        """
            Returns a list of new :class:`~.specimen.models.Specimen`'s loaded
            from `filename`, setting their parent to `parent` using the given
            parser. If the load_as_insitu flag is set to true, 
        """
        specimens = list()
        xrdfiles = parser.parse(filename)
        if len(xrdfiles):
            if getattr(xrdfiles[0], "relative_humidity_data",
                       None) is not None:  # we have relative humidity data
                specimen = None

                # Setup list variables:
                x_data = None
                y_datas = []
                rh_datas = []

                for xrdfile in xrdfiles:
                    # Get data we need:
                    name, sample, xy_data, rh_data = (
                        xrdfile.filename, xrdfile.name, xrdfile.data,
                        xrdfile.relative_humidity_data)
                    # Transform into numpy array for column selection
                    xy_data = np.array(xy_data)
                    rh_data = np.array(rh_data)
                    if specimen is None:
                        specimen = Specimen(parent=parent,
                                            name=name,
                                            sample_name=sample)
                        specimen.goniometer.reset_from_file(
                            xrdfile.create_gon_file())
                        # Extract the 2-theta positions once:
                        x_data = np.copy(xy_data[:, 0])

                    # Add a new sub-pattern:
                    y_datas.append(np.copy(xy_data[:, 1]))

                    # Store the average RH for this pattern:
                    rh_datas.append(np.average(rh_data))

                specimen.experimental_pattern.load_data_from_generator(
                    zip(x_data,
                        np.asanyarray(y_datas).transpose()),
                    clear=True)
                specimen.experimental_pattern.y_names = [
                    "%.1f" % f for f in rh_datas
                ]
                specimen.experimental_pattern.z_data = rh_datas
                specimens.append(specimen)
            else:  # regular (might be multi-pattern) file
                for xrdfile in xrdfiles:
                    name, sample, generator = xrdfile.filename, xrdfile.name, xrdfile.data
                    specimen = Specimen(parent=parent,
                                        name=name,
                                        sample_name=sample)
                    # TODO FIXME:
                    specimen.experimental_pattern.load_data_from_generator(
                        generator, clear=True)
                    specimen.goniometer.reset_from_file(
                        xrdfile.create_gon_file())
                    specimens.append(specimen)
        return specimens

    def json_properties(self):
        props = Storable.json_properties(self)
        props["exclusion_ranges"] = self.exclusion_ranges._serialize_data()
        return props

    def get_export_meta_data(self):
        """ Returns a dictionary with common meta-data used in export functions
            for experimental or calculated data """
        return dict(
            sample=self.label + " " + self.sample_name,
            wavelength=self.goniometer.wavelength,
            radius=self.goniometer.radius,
            divergence=self.goniometer.divergence,
            soller1=self.goniometer.soller1,
            soller2=self.goniometer.soller2,
        )

    # ------------------------------------------------------------
    #      Methods & Functions
    # ------------------------------------------------------------
    def clear_markers(self):
        with self.visuals_changed.hold():
            for marker in list(self.markers)[::-1]:
                self.markers.remove(marker)

    def auto_add_peaks(self, tmodel):
        """
        Automagically add peak markers
        
        *tmodel* a :class:`~specimen.models.ThresholdSelector` model
        """
        threshold = tmodel.sel_threshold
        base = 1 if (tmodel.pattern == "exp") else 2
        data_x, data_y = tmodel.get_xy()
        maxtab, mintab = peakdetect(data_y, data_x, 5,
                                    threshold)  # @UnusedVariable

        mpositions = [marker.position for marker in self.markers]

        with self.visuals_changed.hold():
            i = 1
            for x, y in maxtab:  # @UnusedVariable
                if not x in mpositions:
                    nm = self.goniometer.get_nm_from_2t(x) if x != 0 else 0
                    new_marker = Marker(label="%%.%df" %
                                        (3 + min(int(log(nm, 10)), 0)) % nm,
                                        parent=self,
                                        position=x,
                                        base=base)
                    self.markers.append(new_marker)
                i += 1

    def get_exclusion_selector(self):
        """
        Get the numpy selector array for non-excluded data
        
        :rtype: a numpy ndarray
        """
        x = self.__get_range_theta() * 360.0 / pi  # convert to degrees
        selector = np.ones(x.shape, dtype=bool)
        data = np.sort(np.asarray(self.exclusion_ranges.get_xy_data()), axis=0)
        for x0, x1 in zip(*data):
            new_selector = ((x < x0) | (x > x1))
            selector = selector & new_selector
        return selector

    def get_exclusion_xy(self):
        """
        Get an numpy array containing only non-excluded data X and Y data
                
        :rtype: a tuple containing 4 numpy ndarray's: the experimental X and Y
        data and the calculated X and Y data
        """
        ex, ey = self.experimental_pattern.get_xy_data()
        cx, cy = self.calculated_pattern.get_xy_data()
        selector = self.get_exclusion_selector(ex)
        return ex[selector], ey[selector], cx[selector], cy[selector]

    # ------------------------------------------------------------
    #      Draggable mix-in hook:
    # ------------------------------------------------------------
    def on_pattern_dragged(self, delta_y, button=1):
        if button == 1:
            self.display_vshift += delta_y
        elif button == 3:
            self.display_vscale += delta_y
        elif button == 2:
            self.project.display_plot_offset += delta_y
        pass

    def update_visuals(self, phases):
        """
            Update visual representation of phase patterns (if any)
        """
        if phases is not None:
            self.calculated_pattern.y_names = [
                phase.name if phase is not None else "" for phase in phases
            ]
            self.calculated_pattern.phase_colors = [
                phase.display_color if phase is not None else "#FF00FF"
                for phase in phases
            ]

    # ------------------------------------------------------------
    #      Intensity calculations:
    # ------------------------------------------------------------
    def update_pattern(self, total_intensity, phase_intensities, phases):
        """
        Update calculated patterns using the provided total and phase intensities
        """
        if len(phases) == 0:
            self.calculated_pattern.clear()
        else:
            maxZ = len(self.get_z_list())
            new_data = np.zeros(
                (phase_intensities.shape[-1], maxZ + maxZ * len(phases)))
            for z_index in range(maxZ):
                # Set the total intensity for this z_index:
                new_data[:, z_index] = total_intensity[z_index]
                # Calculate phase intensity offsets:
                phase_start_index = maxZ + z_index * len(phases)
                phase_end_index = phase_start_index + len(phases)
                # Set phase intensities for this z_index:
                new_data[:, phase_start_index:
                         phase_end_index] = phase_intensities[:,
                                                              z_index, :].transpose(
                                                              )
                # Store in pattern:
                self.calculated_pattern.set_data(
                    self.__get_range_theta() * 360. / pi, new_data)
            self.update_visuals(phases)
        if settings.GUI_MODE:
            self.statistics.update_statistics(derived=self.display_derivatives)

    def convert_to_fixed(self):
        """
        Converts the experimental data from ADS to fixed slits in-place 
        (disregards the `has_ads` flag in the goniometer, but uses the settings
        otherwise) 
        """
        correction = self.goniometer.get_ADS_to_fixed_correction(
            self.__get_range_theta())
        self.experimental_pattern.apply_correction(correction)

    def convert_to_ads(self):
        """
        Converts the experimental data from fixed slits to ADS in-place 
        (disregards the `has_ads` flag in the goniometer, but uses the settings
        otherwise) 
        """
        correction = 1.0 / self.goniometer.get_ADS_to_fixed_correction(
            self.__get_range_theta())
        self.experimental_pattern.apply_correction(correction)

    def __get_range_theta(self):
        if len(self.experimental_pattern) <= 1:
            return self.goniometer.get_default_theta_range()
        else:
            return np.radians(self.experimental_pattern.data_x * 0.5)

    def __repr__(self):
        return "Specimen(name='%s')" % self.name

    pass  # end of class
Example #4
0
    def __init__(self, *args, **kwargs):
        """
            Valid keyword arguments for a Specimen are:
                name: the name of the specimen
                sample_name: the sample name of the specimen
                sample_length: the sample length of the specimen
                absorption: the mass absorption of the specimen
                calculated_pattern: the calculated pattern
                experimental_pattern: the experimental pattern
                exclusion_ranges: the exclusion ranges XYListStore
                goniometer: the goniometer used for recording data
                markers: the specimen's markers
                display_vshift: the patterns vertical shift from its default position
                display_vscale: the patterns vertical scale (default is 1.0)
                display_calculated: whether or not to show the calculated pattern
                display_experimental: whether or not to show the experimental pattern
                display_residuals: whether or not to show the residuals
                display_derivatives: whether or not to show the 1st derivative patterns
                display_phases: whether or not to show the separate phase patterns
                display_stats_in_lbl: whether or not to display the Rp values 
                 in the pattern label
        """

        my_kwargs = self.pop_kwargs(
            kwargs, "data_name", "data_sample", "data_sample_length",
            "data_calculated_pattern", "data_experimental_pattern",
            "calc_color", "calc_lw", "inherit_calc_color", "inherit_calc_lw",
            "exp_color", "exp_lw", "inherit_exp_color", "inherit_exp_lw",
            "project_goniometer", "data_markers", "bg_shift", "abs_scale",
            "exp_cap_value",
            *[names[0] for names in self.Meta.get_local_storable_properties()])
        super(Specimen, self).__init__(*args, **kwargs)
        kwargs = my_kwargs

        self._data_object = SpecimenData()

        with self.visuals_changed.hold_and_emit():
            with self.data_changed.hold_and_emit():
                self.name = self.get_kwarg(kwargs, "", "name", "data_name")
                self.sample_name = self.get_kwarg(kwargs, "", "sample_name",
                                                  "data_sample")
                self.sample_length = float(
                    self.get_kwarg(kwargs, settings.SPECIMEN_SAMPLE_LENGTH,
                                   "sample_length", "data_sample_length"))
                self.absorption = float(
                    self.get_kwarg(kwargs, 0.9, "absorption"))

                calc_pattern_old_kwargs = {}
                for kw in ("calc_color", "calc_lw", "inherit_calc_color",
                           "inherit_calc_lw"):
                    if kw in kwargs:
                        calc_pattern_old_kwargs[kw.replace(
                            "calc_", "")] = kwargs.pop(kw)
                self.calculated_pattern = self.parse_init_arg(
                    self.get_kwarg(kwargs, None, "calculated_pattern",
                                   "data_calculated_pattern"),
                    CalculatedLine,
                    child=True,
                    default_is_class=True,
                    label="Calculated Profile",
                    parent=self,
                    **calc_pattern_old_kwargs)

                exp_pattern_old_kwargs = {}
                for kw in ("exp_color", "exp_lw", "inherit_exp_color",
                           "inherit_exp_lw"):
                    if kw in kwargs:
                        exp_pattern_old_kwargs[kw.replace("exp_",
                                                          "")] = kwargs.pop(kw)
                self.experimental_pattern = self.parse_init_arg(
                    self.get_kwarg(kwargs, None, "experimental_pattern",
                                   "data_experimental_pattern"),
                    ExperimentalLine,
                    child=True,
                    default_is_class=True,
                    label="Experimental Profile",
                    parent=self,
                    **exp_pattern_old_kwargs)

                self.exclusion_ranges = PyXRDLine(data=self.get_kwarg(
                    kwargs, None, "exclusion_ranges"),
                                                  parent=self)

                self.goniometer = self.parse_init_arg(
                    self.get_kwarg(kwargs, None, "goniometer",
                                   "project_goniometer"),
                    Goniometer,
                    child=True,
                    default_is_class=True,
                    parent=self,
                )

                self.markers = self.get_list(kwargs,
                                             None,
                                             "markers",
                                             "data_markers",
                                             parent=self)
                for marker in self.markers:
                    self.observe_model(marker)
                self._specimens_observer = ListObserver(
                    self.on_marker_inserted,
                    self.on_marker_removed,
                    prop_name="markers",
                    model=self)

                self.display_vshift = float(
                    self.get_kwarg(kwargs, 0.0, "display_vshift"))
                self.display_vscale = float(
                    self.get_kwarg(kwargs, 1.0, "display_vscale"))
                self.display_calculated = bool(
                    self.get_kwarg(kwargs, True, "display_calculated"))
                self.display_experimental = bool(
                    self.get_kwarg(kwargs, True, "display_experimental"))
                self.display_residuals = bool(
                    self.get_kwarg(kwargs, True, "display_residuals"))
                self.display_residual_scale = float(
                    self.get_kwarg(kwargs, 1.0, "display_residual_scale"))
                self.display_derivatives = bool(
                    self.get_kwarg(kwargs, False, "display_derivatives"))
                self.display_phases = bool(
                    self.get_kwarg(kwargs, False, "display_phases"))
                self.display_stats_in_lbl = bool(
                    self.get_kwarg(kwargs, True, "display_stats_in_lbl"))

                self.statistics = Statistics(parent=self)

                pass  # end of with
            pass  # end of with
        pass  # end of __init__
Example #5
0
class Specimen(DataModel, Storable):
    # MODEL INTEL:
    class Meta(DataModel.Meta):
        properties = [
            PropIntel(name="name",
                      label="Name",
                      data_type=unicode,
                      is_column=True,
                      storable=True,
                      has_widget=True),
            PropIntel(name="sample_name",
                      label="Sample",
                      data_type=unicode,
                      is_column=True,
                      storable=True,
                      has_widget=True),
            PropIntel(name="label",
                      label="Label",
                      data_type=unicode,
                      is_column=True),
            PropIntel(name="sample_length",
                      label="Sample length [cm]",
                      data_type=float,
                      minimum=0.0,
                      is_column=True,
                      storable=True,
                      has_widget=True,
                      widget_type="spin"),
            PropIntel(name="absorption",
                      label="Absorption coeff. (µ*g)",
                      data_type=float,
                      minimum=0.0,
                      is_column=True,
                      storable=True,
                      has_widget=True,
                      widget_type="spin"),
            PropIntel(name="display_calculated",
                      label="Display calculated diffractogram",
                      data_type=bool,
                      is_column=True,
                      storable=True,
                      has_widget=True),
            PropIntel(name="display_experimental",
                      label="Display experimental diffractogram",
                      data_type=bool,
                      is_column=True,
                      storable=True,
                      has_widget=True),
            PropIntel(name="display_phases",
                      label="Display phases seperately",
                      data_type=bool,
                      is_column=True,
                      storable=True,
                      has_widget=True),
            PropIntel(name="display_stats_in_lbl",
                      label="Display Rp in label",
                      data_type=bool,
                      is_column=True,
                      storable=True,
                      has_widget=True),
            PropIntel(name="display_vshift",
                      label="Vertical shift of the plot",
                      data_type=float,
                      is_column=True,
                      storable=True,
                      has_widget=True,
                      widget_type="spin"),
            PropIntel(name="display_vscale",
                      label="Vertical scale of the plot",
                      data_type=float,
                      is_column=True,
                      storable=True,
                      has_widget=True,
                      widget_type="spin"),
            PropIntel(name="display_residuals",
                      label="Display residual patterns",
                      data_type=bool,
                      is_column=True,
                      storable=True,
                      has_widget=True),
            PropIntel(name="display_residual_scale",
                      label="Residual pattern scale",
                      data_type=float,
                      minimum=0.0,
                      is_column=True,
                      storable=True,
                      has_widget=True,
                      widget_type="spin"),
            PropIntel(name="display_derivatives",
                      label="Display derivative patterns",
                      data_type=bool,
                      is_column=True,
                      storable=True,
                      has_widget=True),
            PropIntel(name="goniometer",
                      label="Goniometer",
                      data_type=object,
                      is_column=True,
                      storable=True,
                      has_widget=True,
                      widget_type="custom"),
            PropIntel(name="calculated_pattern",
                      label="Calculated diffractogram",
                      data_type=object,
                      is_column=True,
                      storable=True,
                      has_widget=True,
                      widget_type="xy_list_view"),
            PropIntel(name="experimental_pattern",
                      label="Experimental diffractogram",
                      data_type=object,
                      is_column=True,
                      storable=True,
                      has_widget=True,
                      widget_type="xy_list_view"),
            PropIntel(name="exclusion_ranges",
                      label="Excluded ranges",
                      data_type=object,
                      is_column=True,
                      storable=True,
                      has_widget=True,
                      widget_type="xy_list_view"),
            PropIntel(name="markers",
                      label="Markers",
                      data_type=object,
                      is_column=True,
                      storable=True,
                      widget_type="object_list_view",
                      class_type=Marker),
            PropIntel(name="statistics",
                      label="Statistics",
                      data_type=object,
                      is_column=True),
        ]
        store_id = "Specimen"

        export_filters = xrd_parsers.get_export_file_filters()
        excl_filters = exc_parsers.get_import_file_filters()

    _data_object = None

    @property
    def data_object(self):
        # self._data_object.phases = None #clear this
        self._data_object.goniometer = self.goniometer.data_object
        self._data_object.range_theta = self.__get_range_theta()
        self._data_object.selected_range = self.get_exclusion_selector()
        try:
            self._data_object.observed_intensity = self.experimental_pattern.data_y[:,
                                                                                    0]
        except IndexError:
            self._data_object.observed_intensity = np.array([], dtype=float)
        return self._data_object

    project = property(DataModel.parent.fget, DataModel.parent.fset)

    # PROPERTIES:
    _sample_name = u""

    def get_sample_name(self):
        return self._sample_name

    def set_sample_name(self, value):
        self._sample_name = value
        self.visuals_changed.emit()

    _name = u""

    def get_name(self):
        return self._name

    def set_name(self, value):
        self._name = value
        self.visuals_changed.emit()

    _display_calculated = True

    def get_display_calculated(self):
        return self._display_calculated

    def set_display_calculated(self, value):
        self._display_calculated = bool(value)
        self.visuals_changed.emit()

    _display_experimental = True

    def get_display_experimental(self):
        return self._display_experimental

    def set_display_experimental(self, value):
        self._display_experimental = bool(value)
        self.visuals_changed.emit()

    _display_vshift = 0.0

    def get_display_vshift(self):
        return self._display_vshift

    def set_display_vshift(self, value):
        self._display_vshift = float(value)
        self.visuals_changed.emit()

    _display_vscale = 0.0

    def get_display_vscale(self):
        return self._display_vscale

    def set_display_vscale(self, value):
        self._display_vscale = float(value)
        self.visuals_changed.emit()

    _display_phases = False

    def get_display_phases(self):
        return self._display_phases

    def set_display_phases(self, value):
        self._display_phases = bool(value)
        self.visuals_changed.emit()

    _display_stats_in_lbl = True

    def get_display_stats_in_lbl(self):
        return self._display_stats_in_lbl

    def set_display_stats_in_lbl(self, value):
        self._display_stats_in_lbl = bool(value)
        self.visuals_changed.emit()

    _display_residuals = True

    def get_display_residuals(self):
        return self._display_residuals

    def set_display_residuals(self, value):
        self._display_residuals = bool(value)
        self.visuals_changed.emit()

    _display_derivatives = False

    def get_display_derivatives(self):
        return self._display_derivatives

    def set_display_derivatives(self, value):
        self._display_derivatives = bool(value)
        self.visuals_changed.emit()

    _display_residual_scale = 1.0

    def get_display_residual_scale(self):
        return self._display_residual_scale

    def set_display_residual_scale(self, value):
        self._display_residual_scale = float(value)
        self.visuals_changed.emit()

    def get_label(self):
        label = self.sample_name
        if (self.project is not None and self.project.layout_mode == "FULL"):
            if self.display_stats_in_lbl:
                label += "\nR$_p$ = %.1f%%" % not_none(self.statistics.Rp, 0.0)
                label += "\nR$_{wp}$ = %.1f%%" % not_none(
                    self.statistics.Rwp, 0.0)
            if self.display_residual_scale != 1.0:
                label += "\n\nResidual x%0.1f " % not_none(
                    self.display_residual_scale, 1.0)
        return label

    _calculated_pattern = None

    def get_calculated_pattern(self):
        return self._calculated_pattern

    def set_calculated_pattern(self, value):
        if value != self._calculated_pattern:
            with self.data_changed.hold_and_emit():
                if self._calculated_pattern is not None:
                    self.relieve_model(self._calculated_pattern)
                self._calculated_pattern = value
                if self._calculated_pattern is not None:
                    self.observe_model(self._calculated_pattern)

    _experimental_pattern = None

    def get_experimental_pattern(self):
        return self._experimental_pattern

    def set_experimental_pattern(self, value):
        if value != self._experimental_pattern:
            with self.data_changed.hold_and_emit():
                if self._experimental_pattern is not None:
                    self.relieve_model(self._experimental_pattern)
                self._experimental_pattern = value
                if self._experimental_pattern is not None:
                    self.observe_model(self._experimental_pattern)

    _exclusion_ranges = None

    def get_exclusion_ranges(self):
        return self._exclusion_ranges

    def set_exclusion_ranges(self, value):
        if value != self._exclusion_ranges:
            with self.data_changed.hold_and_emit():
                if self._exclusion_ranges is not None:
                    self.relieve_model(self._exclusion_ranges)
                self._exclusion_ranges = value
                if self._exclusion_ranges is not None:
                    self.observe_model(self._exclusion_ranges)

    _goniometer = None

    def get_goniometer(self):
        return self._goniometer

    def set_goniometer(self, value):
        if value != self._goniometer:
            with self.data_changed.hold_and_emit():
                if self._goniometer is not None:
                    self.relieve_model(self._goniometer)
                self._goniometer = value
                if self._goniometer is not None:
                    self.observe_model(self._goniometer)

    def get_sample_length(self):
        return self._data_object.sample_length

    def set_sample_length(self, value):
        with self.data_changed.hold_and_emit():
            self._data_object.sample_length = float(value)

    def get_absorption(self):
        return self._data_object.absorption

    def set_absorption(self, value):
        with self.data_changed.hold_and_emit():
            self._data_object.absorption = float(value)

    statistics = None

    _markers = []

    def get_markers(self):
        return self._markers

    def set_markers(self, value):
        with self.visuals_changed.hold_and_emit():
            self._markers = value

    @property
    def max_intensity(self):
        """The maximum intensity of the current profile (both calculated and observed"""
        _max = 0.0
        if self.experimental_pattern is not None:
            _max = max(_max, np.max(self.experimental_pattern.max_intensity))
        if self.calculated_pattern is not None:
            _max = max(_max, np.max(self.calculated_pattern.max_intensity))
        return _max

    # ------------------------------------------------------------
    #      Initialisation and other internals
    # ------------------------------------------------------------
    def __init__(self, *args, **kwargs):
        """
            Valid keyword arguments for a Specimen are:
                name: the name of the specimen
                sample_name: the sample name of the specimen
                sample_length: the sample length of the specimen
                absorption: the mass absorption of the specimen
                calculated_pattern: the calculated pattern
                experimental_pattern: the experimental pattern
                exclusion_ranges: the exclusion ranges XYListStore
                goniometer: the goniometer used for recording data
                markers: the specimen's markers
                display_vshift: the patterns vertical shift from its default position
                display_vscale: the patterns vertical scale (default is 1.0)
                display_calculated: whether or not to show the calculated pattern
                display_experimental: whether or not to show the experimental pattern
                display_residuals: whether or not to show the residuals
                display_derivatives: whether or not to show the 1st derivative patterns
                display_phases: whether or not to show the separate phase patterns
                display_stats_in_lbl: whether or not to display the Rp values 
                 in the pattern label
        """

        my_kwargs = self.pop_kwargs(
            kwargs, "data_name", "data_sample", "data_sample_length",
            "data_calculated_pattern", "data_experimental_pattern",
            "calc_color", "calc_lw", "inherit_calc_color", "inherit_calc_lw",
            "exp_color", "exp_lw", "inherit_exp_color", "inherit_exp_lw",
            "project_goniometer", "data_markers", "bg_shift", "abs_scale",
            "exp_cap_value",
            *[names[0] for names in self.Meta.get_local_storable_properties()])
        super(Specimen, self).__init__(*args, **kwargs)
        kwargs = my_kwargs

        self._data_object = SpecimenData()

        with self.visuals_changed.hold_and_emit():
            with self.data_changed.hold_and_emit():
                self.name = self.get_kwarg(kwargs, "", "name", "data_name")
                self.sample_name = self.get_kwarg(kwargs, "", "sample_name",
                                                  "data_sample")
                self.sample_length = float(
                    self.get_kwarg(kwargs, settings.SPECIMEN_SAMPLE_LENGTH,
                                   "sample_length", "data_sample_length"))
                self.absorption = float(
                    self.get_kwarg(kwargs, 0.9, "absorption"))

                calc_pattern_old_kwargs = {}
                for kw in ("calc_color", "calc_lw", "inherit_calc_color",
                           "inherit_calc_lw"):
                    if kw in kwargs:
                        calc_pattern_old_kwargs[kw.replace(
                            "calc_", "")] = kwargs.pop(kw)
                self.calculated_pattern = self.parse_init_arg(
                    self.get_kwarg(kwargs, None, "calculated_pattern",
                                   "data_calculated_pattern"),
                    CalculatedLine,
                    child=True,
                    default_is_class=True,
                    label="Calculated Profile",
                    parent=self,
                    **calc_pattern_old_kwargs)

                exp_pattern_old_kwargs = {}
                for kw in ("exp_color", "exp_lw", "inherit_exp_color",
                           "inherit_exp_lw"):
                    if kw in kwargs:
                        exp_pattern_old_kwargs[kw.replace("exp_",
                                                          "")] = kwargs.pop(kw)
                self.experimental_pattern = self.parse_init_arg(
                    self.get_kwarg(kwargs, None, "experimental_pattern",
                                   "data_experimental_pattern"),
                    ExperimentalLine,
                    child=True,
                    default_is_class=True,
                    label="Experimental Profile",
                    parent=self,
                    **exp_pattern_old_kwargs)

                self.exclusion_ranges = PyXRDLine(data=self.get_kwarg(
                    kwargs, None, "exclusion_ranges"),
                                                  parent=self)

                self.goniometer = self.parse_init_arg(
                    self.get_kwarg(kwargs, None, "goniometer",
                                   "project_goniometer"),
                    Goniometer,
                    child=True,
                    default_is_class=True,
                    parent=self,
                )

                self.markers = self.get_list(kwargs,
                                             None,
                                             "markers",
                                             "data_markers",
                                             parent=self)
                for marker in self.markers:
                    self.observe_model(marker)
                self._specimens_observer = ListObserver(
                    self.on_marker_inserted,
                    self.on_marker_removed,
                    prop_name="markers",
                    model=self)

                self.display_vshift = float(
                    self.get_kwarg(kwargs, 0.0, "display_vshift"))
                self.display_vscale = float(
                    self.get_kwarg(kwargs, 1.0, "display_vscale"))
                self.display_calculated = bool(
                    self.get_kwarg(kwargs, True, "display_calculated"))
                self.display_experimental = bool(
                    self.get_kwarg(kwargs, True, "display_experimental"))
                self.display_residuals = bool(
                    self.get_kwarg(kwargs, True, "display_residuals"))
                self.display_residual_scale = float(
                    self.get_kwarg(kwargs, 1.0, "display_residual_scale"))
                self.display_derivatives = bool(
                    self.get_kwarg(kwargs, False, "display_derivatives"))
                self.display_phases = bool(
                    self.get_kwarg(kwargs, False, "display_phases"))
                self.display_stats_in_lbl = bool(
                    self.get_kwarg(kwargs, True, "display_stats_in_lbl"))

                self.statistics = Statistics(parent=self)

                pass  # end of with
            pass  # end of with
        pass  # end of __init__

    def __str__(self):
        return "<Specimen %s(%s)>" % (self.name, repr(self))

    # ------------------------------------------------------------
    #      Notifications of observable properties
    # ------------------------------------------------------------
    @DataModel.observe("data_changed", signal=True)
    def notify_data_changed(self, model, prop_name, info):
        if model == self.calculated_pattern:
            self.visuals_changed.emit()  # don't propagate this as data_changed
        else:
            self.data_changed.emit()  # propagate signal

    @DataModel.observe("visuals_changed", signal=True)
    def notify_visuals_changed(self, model, prop_name, info):
        self.visuals_changed.emit()  # propagate signal

    def on_marker_removed(self, item):
        with self.visuals_changed.hold_and_emit():
            self.relieve_model(item)
            item.parent = None

    def on_marker_inserted(self, item):
        with self.visuals_changed.hold_and_emit():
            self.observe_model(item)
            item.parent = self

    # ------------------------------------------------------------
    #      Input/Output stuff
    # ------------------------------------------------------------
    @staticmethod
    def from_experimental_data(filename,
                               parent,
                               parser=xrd_parsers._group_parser):
        """
            Returns a list of new :class:`~.specimen.models.Specimen`'s loaded
            from `filename`, setting their parent to `parent` using the given
            parser.
        """
        specimens = list()
        xrdfiles = parser.parse(filename)
        for xrdfile in xrdfiles:
            name, sample, generator = xrdfile.filename, xrdfile.name, xrdfile.data
            specimen = Specimen(parent=parent, name=name, sample_name=sample)
            specimen.experimental_pattern.load_data_from_generator(generator,
                                                                   clear=True)
            specimen.goniometer.reset_from_file(xrdfile.create_gon_file())
            specimens.append(specimen)

        return specimens

    def json_properties(self):
        props = Storable.json_properties(self)
        props["exclusion_ranges"] = self.exclusion_ranges._serialize_data()
        return props

    def get_export_meta_data(self):
        """ Returns a dictionary with common meta-data used in export functions
            for experimental or calculated data """
        return dict(
            sample=self.label + " " + self.sample_name,
            wavelength=self.goniometer.wavelength,
            radius=self.goniometer.radius,
            divergence=self.goniometer.divergence,
            soller1=self.goniometer.soller1,
            soller2=self.goniometer.soller2,
        )

    # ------------------------------------------------------------
    #      Methods & Functions
    # ------------------------------------------------------------
    def clear_markers(self):
        with self.visuals_changed.hold():
            self.markers[:] = []
            # for marker in list(self.markers)[::-1]:
            #    self.markers.remove(marker)

    def auto_add_peaks(self, tmodel):
        """
        Automagically add peak markers
        
        *tmodel* a :class:`~specimen.models.ThresholdSelector` model
        """
        threshold = tmodel.sel_threshold
        base = 1 if (tmodel.pattern == "exp") else 2
        data_x, data_y = tmodel.get_xy()
        maxtab, mintab = peakdetect(data_y, data_x, 5,
                                    threshold)  # @UnusedVariable

        mpositions = [marker.position for marker in self.markers]

        with self.visuals_changed.hold():
            i = 1
            for x, y in maxtab:  # @UnusedVariable
                if not x in mpositions:
                    nm = self.goniometer.get_nm_from_2t(x) if x != 0 else 0
                    new_marker = Marker(label="%%.%df" %
                                        (3 + min(int(log(nm, 10)), 0)) % nm,
                                        parent=self,
                                        position=x,
                                        base=base)
                    self.markers.append(new_marker)
                i += 1

    def get_exclusion_selector(self):
        """
        Get the numpy selector array for non-excluded data
        
        :rtype: a numpy ndarray
        """
        x = self.__get_range_theta() * 360.0 / pi  # convert to degrees
        selector = np.ones(x.shape, dtype=bool)
        data = np.sort(np.asarray(self.exclusion_ranges.get_xy_data()), axis=0)
        for x0, x1 in zip(*data):
            new_selector = ((x < x0) | (x > x1))
            selector = selector & new_selector
        return selector

    def get_exclusion_xy(self):
        """
        Get an numpy array containing only non-excluded data X and Y data
                
        :rtype: a tuple containing 4 numpy ndarray's: the experimental X and Y
        data and the calculated X and Y data
        """
        ex, ey = self.experimental_pattern.get_xy_data()
        cx, cy = self.calculated_pattern.get_xy_data()
        selector = self.get_exclusion_selector(ex)
        return ex[selector], ey[selector], cx[selector], cy[selector]

    # ------------------------------------------------------------
    #      Draggable mix-in hook:
    # ------------------------------------------------------------
    def on_pattern_dragged(self, delta_y, button=1):
        if button == 1:
            self.display_vshift += delta_y
        elif button == 3:
            self.display_vscale += delta_y
        elif button == 2:
            self.project.display_plot_offset += delta_y
        pass

    def update_visuals(self, phases):
        """
            Update visual representation of phase patterns (if any)
        """
        if phases is not None:
            self.calculated_pattern.y_names = [
                phase.name if phase is not None else "" for phase in phases
            ]
            self.calculated_pattern.phase_colors = [
                phase.display_color if phase is not None else "#FF00FF"
                for phase in phases
            ]

    # ------------------------------------------------------------
    #      Intensity calculations:
    # ------------------------------------------------------------
    def update_pattern(self, total_intensity, phase_intensities, phases):
        """
        Update calculated patterns using the provided total and phase intensities
        """
        if len(phases) == 0:
            self.calculated_pattern.clear()
        else:
            self.calculated_pattern.set_data(
                self.__get_range_theta() * 360. / pi,
                np.vstack((total_intensity, phase_intensities)).transpose())
            self.update_visuals(phases)
        if settings.GUI_MODE:
            self.statistics.update_statistics(derived=self.display_derivatives)

    def get_phase_intensities(self, phases):
        """
        Gets phase intensities for the provided phases
        
        *phases* a list of phases with length N
        
        :rtype: a 2-tuple containing 2-theta values and phase intensities
        """
        return calculate_phase_intensities(self.data_object, phases)

    def convert_to_fixed(self):
        """
        Converts the experimental data from ADS to fixed slits in-place 
        (disregards the `has_ads` flag in the goniometer, but uses the settings
        otherwise) 
        """
        correction = self.goniometer.get_ADS_to_fixed_correction(
            self.__get_range_theta())
        self.experimental_pattern.apply_correction(correction)

    def convert_to_ads(self):
        """
        Converts the experimental data from fixed slits to ADS in-place 
        (disregards the `has_ads` flag in the goniometer, but uses the settings
        otherwise) 
        """
        correction = 1.0 / self.goniometer.get_ADS_to_fixed_correction(
            self.__get_range_theta())
        self.experimental_pattern.apply_correction(correction)

    def __get_range_theta(self):
        if len(self.experimental_pattern) <= 1:
            return self.goniometer.get_default_theta_range()
        else:
            return np.radians(self.experimental_pattern.data_x * 0.5)

    def __repr__(self):
        return "Specimen(name='%s')" % self.name

    pass  # end of class
Example #6
0
class Specimen(DataModel, Storable):
    # MODEL INTEL:
    class Meta(DataModel.Meta):
        properties = [
            PropIntel(name="name", label="Name", data_type=unicode, is_column=True, storable=True, has_widget=True),
            PropIntel(name="sample_name", label="Sample", data_type=unicode, is_column=True, storable=True, has_widget=True),
            PropIntel(name="label", label="Label", data_type=unicode, is_column=True),
            PropIntel(name="sample_length", label="Sample length [cm]", data_type=float, minimum=0.0, is_column=True, storable=True, has_widget=True, widget_type="spin"),
            PropIntel(name="absorption", label="Absorption coeff. (µ*g)", data_type=float, minimum=0.0, is_column=True, storable=True, has_widget=True, widget_type="spin"),
            PropIntel(name="display_calculated", label="Display calculated diffractogram", data_type=bool, is_column=True, storable=True, has_widget=True),
            PropIntel(name="display_experimental", label="Display experimental diffractogram", data_type=bool, is_column=True, storable=True, has_widget=True),
            PropIntel(name="display_phases", label="Display phases seperately", data_type=bool, is_column=True, storable=True, has_widget=True),
            PropIntel(name="display_stats_in_lbl", label="Display Rp in label", data_type=bool, is_column=True, storable=True, has_widget=True),
            PropIntel(name="display_vshift", label="Vertical shift of the plot", data_type=float, is_column=True, storable=True, has_widget=True, widget_type="spin"),
            PropIntel(name="display_vscale", label="Vertical scale of the plot", data_type=float, is_column=True, storable=True, has_widget=True, widget_type="spin"),
            PropIntel(name="display_residuals", label="Display residual patterns", data_type=bool, is_column=True, storable=True, has_widget=True),
            PropIntel(name="display_residual_scale", label="Residual pattern scale", data_type=float, minimum=0.0, is_column=True, storable=True, has_widget=True, widget_type="spin"),
            PropIntel(name="display_derivatives", label="Display derivative patterns", data_type=bool, is_column=True, storable=True, has_widget=True),
            PropIntel(name="goniometer", label="Goniometer", data_type=object, is_column=True, storable=True, has_widget=True, widget_type="custom"),
            PropIntel(name="calculated_pattern", label="Calculated diffractogram", data_type=object, is_column=True, storable=True, has_widget=True, widget_type="xy_list_view"),
            PropIntel(name="experimental_pattern", label="Experimental diffractogram", data_type=object, is_column=True, storable=True, has_widget=True, widget_type="xy_list_view"),
            PropIntel(name="exclusion_ranges", label="Excluded ranges", data_type=object, is_column=True, storable=True, has_widget=True, widget_type="xy_list_view"),
            PropIntel(name="markers", label="Markers", data_type=object, is_column=True, storable=True, widget_type="object_list_view", class_type=Marker),
            PropIntel(name="statistics", label="Statistics", data_type=object, is_column=True),
        ]
        store_id = "Specimen"

        file_filters = [parser.file_filter for parser in parsers["xrd"]]
        export_filters = [parser.file_filter for parser in parsers["xrd"] if parser.can_write]
        excl_filters = [parser.file_filter for parser in parsers["exc"]]

    _data_object = None
    @property
    def data_object(self):
        # self._data_object.phases = None #clear this
        self._data_object.goniometer = self.goniometer.data_object
        self._data_object.range_theta = self.__get_range_theta()
        self._data_object.selected_range = self.get_exclusion_selector()
        try:
            self._data_object.observed_intensity = self.experimental_pattern.data_y[:, 0]
        except IndexError:
            self._data_object.observed_intensity = np.array([], dtype=float)
        return self._data_object

    project = property(DataModel.parent.fget, DataModel.parent.fset)

    # PROPERTIES:
    _sample_name = u""
    def get_sample_name(self): return self._sample_name
    def set_sample_name(self, value):
        self._sample_name = value
        self.visuals_changed.emit()

    _name = u""
    def get_name(self): return self._name
    def set_name(self, value):
        self._name = value
        self.visuals_changed.emit()

    _display_calculated = True
    def get_display_calculated(self): return self._display_calculated
    def set_display_calculated(self, value):
        self._display_calculated = bool(value)
        self.visuals_changed.emit()

    _display_experimental = True
    def get_display_experimental(self): return self._display_experimental
    def set_display_experimental(self, value):
        self._display_experimental = bool(value)
        self.visuals_changed.emit()

    _display_vshift = 0.0
    def get_display_vshift(self): return self._display_vshift
    def set_display_vshift(self, value):
        self._display_vshift = float(value)
        self.visuals_changed.emit()

    _display_vscale = 0.0
    def get_display_vscale(self): return self._display_vscale
    def set_display_vscale(self, value):
        self._display_vscale = float(value)
        self.visuals_changed.emit()

    _display_phases = False
    def get_display_phases(self): return self._display_phases
    def set_display_phases(self, value):
        self._display_phases = bool(value)
        self.visuals_changed.emit()

    _display_stats_in_lbl = True
    def get_display_stats_in_lbl(self): return self._display_stats_in_lbl
    def set_display_stats_in_lbl(self, value):
        self._display_stats_in_lbl = bool(value)
        self.visuals_changed.emit()

    _display_residuals = True
    def get_display_residuals(self): return self._display_residuals
    def set_display_residuals(self, value):
        self._display_residuals = bool(value)
        self.visuals_changed.emit()

    _display_derivatives = False
    def get_display_derivatives(self): return self._display_derivatives
    def set_display_derivatives(self, value):
        self._display_derivatives = bool(value)
        self.visuals_changed.emit()

    _display_residual_scale = 1.0
    def get_display_residual_scale(self): return self._display_residual_scale
    def set_display_residual_scale(self, value):
        self._display_residual_scale = float(value)
        self.visuals_changed.emit()

    def get_label(self):
        label = self.sample_name
        if (self.project is not None and self.project.layout_mode == "FULL"):
            if self.display_stats_in_lbl:
                label += "\nR$_p$ = %.1f%%" % not_none(self.statistics.Rp, 0.0)
                label += "\nR$_{wp}$ = %.1f%%" % not_none(self.statistics.Rwp, 0.0)
            if self.display_residual_scale != 1.0:
                label += "\n\nResidual x%0.1f " % not_none(self.display_residual_scale, 1.0)
        return label

    _calculated_pattern = None
    def get_calculated_pattern(self): return self._calculated_pattern
    def set_calculated_pattern(self, value):
        if value != self._calculated_pattern:
            with self.data_changed.hold_and_emit():
                if self._calculated_pattern is not None: self.relieve_model(self._calculated_pattern)
                self._calculated_pattern = value
                if self._calculated_pattern is not None:
                    self.observe_model(self._calculated_pattern)

    _experimental_pattern = None
    def get_experimental_pattern(self): return self._experimental_pattern
    def set_experimental_pattern(self, value):
        if value != self._experimental_pattern:
            with self.data_changed.hold_and_emit():
                if self._experimental_pattern is not None:
                    self.relieve_model(self._experimental_pattern)
                self._experimental_pattern = value
                if self._experimental_pattern is not None:
                    self.observe_model(self._experimental_pattern)

    _exclusion_ranges = None
    def get_exclusion_ranges(self): return self._exclusion_ranges
    def set_exclusion_ranges(self, value):
        if value != self._exclusion_ranges:
            with self.data_changed.hold_and_emit():
                if self._exclusion_ranges is not None:
                    self.relieve_model(self._exclusion_ranges)
                self._exclusion_ranges = value
                if self._exclusion_ranges is not None:
                    self.observe_model(self._exclusion_ranges)


    _goniometer = None
    def get_goniometer(self): return self._goniometer
    def set_goniometer(self, value):
        if value != self._goniometer:
            with self.data_changed.hold_and_emit():
                if self._goniometer is not None:
                    self.relieve_model(self._goniometer)
                self._goniometer = value
                if self._goniometer is not None:
                    self.observe_model(self._goniometer)

    def get_sample_length(self): return self._data_object.sample_length
    def set_sample_length(self, value):
        with self.data_changed.hold_and_emit():
            self._data_object.sample_length = float(value)

    def get_absorption(self): return self._data_object.absorption
    def set_absorption(self, value):
        with self.data_changed.hold_and_emit():
            self._data_object.absorption = float(value)

    statistics = None

    _markers = []
    def get_markers(self): return self._markers
    def set_markers(self, value):
        with self.visuals_changed.hold_and_emit():
            self._markers = value

    @property
    def max_intensity(self):
        """The maximum intensity of the current profile (both calculated and observed"""
        _max = 0.0
        if self.experimental_pattern is not None:
            _max = max(_max, np.max(self.experimental_pattern.max_intensity))
        if self.calculated_pattern is not None:
            _max = max(_max, np.max(self.calculated_pattern.max_intensity))
        return _max

    # ------------------------------------------------------------
    #      Initialisation and other internals
    # ------------------------------------------------------------
    def __init__(self, *args, **kwargs):
        """
            Valid keyword arguments for a Specimen are:
                name: the name of the specimen
                sample_name: the sample name of the specimen
                sample_length: the sample length of the specimen
                absorption: the mass absorption of the specimen
                calculated_pattern: the calculated pattern
                experimental_pattern: the experimental pattern
                exclusion_ranges: the exclusion ranges XYListStore
                goniometer: the goniometer used for recording data
                markers: the specimen's markers
                display_vshift: the patterns vertical shift from its default position
                display_vscale: the patterns vertical scale (default is 1.0)
                display_calculated: whether or not to show the calculated pattern
                display_experimental: whether or not to show the experimental pattern
                display_residuals: whether or not to show the residuals
                display_derivatives: whether or not to show the 1st derivative patterns
                display_phases: whether or not to show the separate phase patterns
                display_stats_in_lbl: whether or not to display the Rp values 
                 in the pattern label
        """

        my_kwargs = self.pop_kwargs(kwargs,
            "data_name", "data_sample", "data_sample_length",
            "data_calculated_pattern", "data_experimental_pattern",
            "calc_color", "calc_lw", "inherit_calc_color", "inherit_calc_lw",
            "exp_color", "exp_lw", "inherit_exp_color", "inherit_exp_lw",
            "project_goniometer", "data_markers", "bg_shift", "abs_scale",
            "exp_cap_value",
            *[names[0] for names in self.Meta.get_local_storable_properties()]
        )
        super(Specimen, self).__init__(*args, **kwargs)
        kwargs = my_kwargs

        self._data_object = SpecimenData()

        with self.visuals_changed.hold_and_emit():
            with self.data_changed.hold_and_emit():
                self.name = self.get_kwarg(kwargs, "", "name", "data_name")
                self.sample_name = self.get_kwarg(kwargs, "", "sample_name", "data_sample")
                self.sample_length = float(self.get_kwarg(kwargs, 1.25, "sample_length", "data_sample_length"))
                self.absorption = float(self.get_kwarg(kwargs, 0.9, "absorption"))

                calc_pattern_old_kwargs = {}
                for kw in ("calc_color", "calc_lw", "inherit_calc_color", "inherit_calc_lw"):
                    if kw in kwargs:
                        calc_pattern_old_kwargs[kw.replace("calc_", "")] = kwargs.pop(kw)
                self.calculated_pattern = self.parse_init_arg(
                    self.get_kwarg(kwargs, None, "calculated_pattern", "data_calculated_pattern"),
                    CalculatedLine,
                    child=True, default_is_class=True,
                    label="Calculated Profile",
                    parent=self,
                    **calc_pattern_old_kwargs
                )

                exp_pattern_old_kwargs = {}
                for kw in ("exp_color", "exp_lw", "inherit_exp_color", "inherit_exp_lw"):
                    if kw in kwargs:
                        exp_pattern_old_kwargs[kw.replace("exp_", "")] = kwargs.pop(kw)
                self.experimental_pattern = self.parse_init_arg(
                    self.get_kwarg(kwargs, None, "experimental_pattern", "data_experimental_pattern"),
                    ExperimentalLine,
                    child=True, default_is_class=True,
                    label="Experimental Profile",
                    parent=self,
                    **exp_pattern_old_kwargs
                )

                self.exclusion_ranges = PyXRDLine(data=self.get_kwarg(kwargs, None, "exclusion_ranges"), parent=self)

                self.goniometer = self.parse_init_arg(
                    self.get_kwarg(kwargs, None, "goniometer", "project_goniometer"),
                    Goniometer, child=True, default_is_class=True,
                    parent=self,
                )

                self.markers = self.get_list(kwargs, None, "markers", "data_markers", parent=self)
                for marker in self.markers:
                    self.observe_model(marker)
                self._specimens_observer = ListObserver(
                    self.on_marker_inserted,
                    self.on_marker_removed,
                    prop_name="markers",
                    model=self
                )

                self.display_vshift = float(self.get_kwarg(kwargs, 0.0, "display_vshift"))
                self.display_vscale = float(self.get_kwarg(kwargs, 1.0, "display_vscale"))
                self.display_calculated = bool(self.get_kwarg(kwargs, True, "display_calculated"))
                self.display_experimental = bool(self.get_kwarg(kwargs, True, "display_experimental"))
                self.display_residuals = bool(self.get_kwarg(kwargs, True, "display_residuals"))
                self.display_residual_scale = float(self.get_kwarg(kwargs, 1.0, "display_residual_scale"))
                self.display_derivatives = bool(self.get_kwarg(kwargs, False, "display_derivatives"))
                self.display_phases = bool(self.get_kwarg(kwargs, False, "display_phases"))
                self.display_stats_in_lbl = bool(self.get_kwarg(kwargs, True, "display_stats_in_lbl"))

                self.statistics = Statistics(parent=self)

                pass # end of with
            pass # end of with
        pass # end of __init__

    def __str__(self):
        return "<Specimen %s(%s)>" % (self.name, repr(self))

    # ------------------------------------------------------------
    #      Notifications of observable properties
    # ------------------------------------------------------------
    @DataModel.observe("data_changed", signal=True)
    def notify_data_changed(self, model, prop_name, info):
        if model == self.calculated_pattern:
            self.visuals_changed.emit() # don't propagate this as data_changed
        else:
            self.data_changed.emit() # propagate signal

    @DataModel.observe("visuals_changed", signal=True)
    def notify_visuals_changed(self, model, prop_name, info):
        self.visuals_changed.emit() # propagate signal

    def on_marker_removed(self, item):
        with self.visuals_changed.hold_and_emit():
            self.relieve_model(item)
            item.parent = None

    def on_marker_inserted(self, item):
        with self.visuals_changed.hold_and_emit():
            self.observe_model(item)
            item.parent = self

    # ------------------------------------------------------------
    #      Input/Output stuff
    # ------------------------------------------------------------
    @staticmethod
    def from_experimental_data(filename, parent, parser=None):
        """
            Returns a list of new :class:`~.specimen.models.Specimen`'s loaded
            from `filename`, setting thjeir parent to `parent` using the given
            parser or :class:`~.generic.io.xrd_parsers.XRDParser` if none passed.
        """
        specimens = list()
        parser = not_none(parser, XRDParser)
        xrdfiles = parser.parse(filename)
        for xrdfile in xrdfiles:
            name, sample, generator = xrdfile.filename, xrdfile.name, xrdfile.data
            specimen = Specimen(parent=parent, name=name, sample_name=sample)
            specimen.experimental_pattern.load_data_from_generator(generator, clear=True)
            specimen.goniometer.reset_from_file(None, data=xrdfile.create_gon_file())
            specimens.append(specimen)

        return specimens

    def json_properties(self):
        props = Storable.json_properties(self)
        props["exclusion_ranges"] = self.exclusion_ranges._serialize_data()
        return props

    def get_export_meta_data(self):
        """ Returns a dictionary with common meta-data used in export functions
            for experimental or calculated data """
        return dict(
            sample=self.label + " " + self.sample_name,
            wavelength=self.goniometer.wavelength,
            radius=self.goniometer.radius,
            divergence=self.goniometer.divergence,
            soller1=self.goniometer.soller1,
            soller2=self.goniometer.soller2,
        )


    # ------------------------------------------------------------
    #      Methods & Functions
    # ------------------------------------------------------------
    def clear_markers(self):
        with self.visuals_changed.hold():
            self.markers[:] = []
            # for marker in list(self.markers)[::-1]:
            #    self.markers.remove(marker)

    def auto_add_peaks(self, tmodel):
        """
        Automagically add peak markers
        
        *tmodel* a :class:`~specimen.models.ThresholdSelector` model
        """
        threshold = tmodel.sel_threshold
        base = 1 if (tmodel.pattern == "exp") else 2
        data_x, data_y = tmodel.get_xy()
        maxtab, mintab = peakdetect(data_y, data_x, 5, threshold) # @UnusedVariable

        mpositions = [marker.position for marker in self.markers]

        with self.visuals_changed.hold():
            i = 1
            for x, y in maxtab: # @UnusedVariable
                if not x in mpositions:
                    nm = self.goniometer.get_nm_from_2t(x) if x != 0 else 0
                    new_marker = Marker(label="%%.%df" % (3 + min(int(log(nm, 10)), 0)) % nm, parent=self, position=x, base=base)
                    self.markers.append(new_marker)
                i += 1

    def get_exclusion_selector(self):
        """
        Get the numpy selector array for non-excluded data
        
        :rtype: a numpy ndarray
        """
        x = self.__get_range_theta() * 360.0 / pi # convert to degrees
        selector = np.ones(x.shape, dtype=bool)
        data = np.sort(np.asarray(self.exclusion_ranges.get_xy_data()), axis=0)
        for x0, x1 in zip(*data):
            new_selector = ((x < x0) | (x > x1))
            selector = selector & new_selector
        return selector

    def get_exclusion_xy(self):
        """
        Get an numpy array containing only non-excluded data X and Y data
                
        :rtype: a tuple containing 4 numpy ndarray's: the experimental X and Y
        data and the calculated X and Y data
        """
        ex, ey = self.experimental_pattern.get_xy_data()
        cx, cy = self.calculated_pattern.get_xy_data()
        selector = self.get_exclusion_selector(ex)
        return ex[selector], ey[selector], cx[selector], cy[selector]

    # ------------------------------------------------------------
    #      Draggable mix-in hook:
    # ------------------------------------------------------------
    def on_pattern_dragged(self, delta_y, button=1):
        if button == 1:
            self.display_vshift += delta_y
        elif button == 3:
            self.display_vscale += delta_y
        elif button == 2:
            self.project.display_plot_offset += delta_y
        pass

    def update_visuals(self, phases):
        """
            Update visual representation of phase patterns (if any)
        """
        if phases is not None:
            self.calculated_pattern.y_names = [
               phase.name if phase is not None else "" for phase in phases
            ]
            self.calculated_pattern.phase_colors = [
                phase.display_color if phase is not None else "#FF00FF" for phase in phases
            ]

    # ------------------------------------------------------------
    #      Intensity calculations:
    # ------------------------------------------------------------
    def update_pattern(self, total_intensity, phase_intensities, phases):
        """
        Update calculated patterns using the provided total and phase intensities
        """
        if len(phases) == 0:
            self.calculated_pattern.clear()
        else:
            self.calculated_pattern.set_data(
                 self.__get_range_theta() * 360. / pi,
                 np.vstack((total_intensity, phase_intensities)).transpose()
            )
            self.update_visuals(phases)
        if settings.GUI_MODE:
            self.statistics.update_statistics(derived=self.display_derivatives)

    def get_phase_intensities(self, phases):
        """
        Gets phase intensities for the provided phases
        
        *phases* a list of phases with length N
        
        :rtype: a 2-tuple containing 2-theta values and phase intensities
        """
        return calculate_phase_intensities(self.data_object, phases)

    def convert_to_fixed(self):
        """
        Converts the experimental data from ADS to fixed slits in-place 
        (disregards the `has_ads` flag in the goniometer, but uses the settings
        otherwise) 
        """
        correction = self.goniometer.get_ADS_to_fixed_correction(self.__get_range_theta())
        self.experimental_pattern.apply_correction(correction)

    def convert_to_ads(self):
        """
        Converts the experimental data from fixed slits to ADS in-place 
        (disregards the `has_ads` flag in the goniometer, but uses the settings
        otherwise) 
        """
        correction = 1.0 / self.goniometer.get_ADS_to_fixed_correction(self.__get_range_theta())
        self.experimental_pattern.apply_correction(correction)

    def __get_range_theta(self):
        if len(self.experimental_pattern) <= 1:
            return self.goniometer.get_default_theta_range()
        else:
            return np.radians(self.experimental_pattern.data_x * 0.5)

    def __repr__(self):
        return "Specimen(name='%s')" % self.name

    pass # end of class
Example #7
0
    def __init__(self, *args, **kwargs):
        """
            Valid keyword arguments for a Specimen are:
                name: the name of the specimen
                sample_name: the sample name of the specimen
                sample_length: the sample length of the specimen
                absorption: the mass absorption of the specimen
                calculated_pattern: the calculated pattern
                experimental_pattern: the experimental pattern
                exclusion_ranges: the exclusion ranges XYListStore
                goniometer: the goniometer used for recording data
                markers: the specimen's markers
                display_vshift: the patterns vertical shift from its default position
                display_vscale: the patterns vertical scale (default is 1.0)
                display_calculated: whether or not to show the calculated pattern
                display_experimental: whether or not to show the experimental pattern
                display_residuals: whether or not to show the residuals
                display_derivatives: whether or not to show the 1st derivative patterns
                display_phases: whether or not to show the separate phase patterns
                display_stats_in_lbl: whether or not to display the Rp values 
                 in the pattern label
        """

        my_kwargs = self.pop_kwargs(kwargs,
            "data_name", "data_sample", "data_sample_length",
            "data_calculated_pattern", "data_experimental_pattern",
            "calc_color", "calc_lw", "inherit_calc_color", "inherit_calc_lw",
            "exp_color", "exp_lw", "inherit_exp_color", "inherit_exp_lw",
            "project_goniometer", "data_markers", "bg_shift", "abs_scale",
            "exp_cap_value",
            *[names[0] for names in self.Meta.get_local_storable_properties()]
        )
        super(Specimen, self).__init__(*args, **kwargs)
        kwargs = my_kwargs

        self._data_object = SpecimenData()

        with self.visuals_changed.hold_and_emit():
            with self.data_changed.hold_and_emit():
                self.name = self.get_kwarg(kwargs, "", "name", "data_name")
                self.sample_name = self.get_kwarg(kwargs, "", "sample_name", "data_sample")
                self.sample_length = float(self.get_kwarg(kwargs, 1.25, "sample_length", "data_sample_length"))
                self.absorption = float(self.get_kwarg(kwargs, 0.9, "absorption"))

                calc_pattern_old_kwargs = {}
                for kw in ("calc_color", "calc_lw", "inherit_calc_color", "inherit_calc_lw"):
                    if kw in kwargs:
                        calc_pattern_old_kwargs[kw.replace("calc_", "")] = kwargs.pop(kw)
                self.calculated_pattern = self.parse_init_arg(
                    self.get_kwarg(kwargs, None, "calculated_pattern", "data_calculated_pattern"),
                    CalculatedLine,
                    child=True, default_is_class=True,
                    label="Calculated Profile",
                    parent=self,
                    **calc_pattern_old_kwargs
                )

                exp_pattern_old_kwargs = {}
                for kw in ("exp_color", "exp_lw", "inherit_exp_color", "inherit_exp_lw"):
                    if kw in kwargs:
                        exp_pattern_old_kwargs[kw.replace("exp_", "")] = kwargs.pop(kw)
                self.experimental_pattern = self.parse_init_arg(
                    self.get_kwarg(kwargs, None, "experimental_pattern", "data_experimental_pattern"),
                    ExperimentalLine,
                    child=True, default_is_class=True,
                    label="Experimental Profile",
                    parent=self,
                    **exp_pattern_old_kwargs
                )

                self.exclusion_ranges = PyXRDLine(data=self.get_kwarg(kwargs, None, "exclusion_ranges"), parent=self)

                self.goniometer = self.parse_init_arg(
                    self.get_kwarg(kwargs, None, "goniometer", "project_goniometer"),
                    Goniometer, child=True, default_is_class=True,
                    parent=self,
                )

                self.markers = self.get_list(kwargs, None, "markers", "data_markers", parent=self)
                for marker in self.markers:
                    self.observe_model(marker)
                self._specimens_observer = ListObserver(
                    self.on_marker_inserted,
                    self.on_marker_removed,
                    prop_name="markers",
                    model=self
                )

                self.display_vshift = float(self.get_kwarg(kwargs, 0.0, "display_vshift"))
                self.display_vscale = float(self.get_kwarg(kwargs, 1.0, "display_vscale"))
                self.display_calculated = bool(self.get_kwarg(kwargs, True, "display_calculated"))
                self.display_experimental = bool(self.get_kwarg(kwargs, True, "display_experimental"))
                self.display_residuals = bool(self.get_kwarg(kwargs, True, "display_residuals"))
                self.display_residual_scale = float(self.get_kwarg(kwargs, 1.0, "display_residual_scale"))
                self.display_derivatives = bool(self.get_kwarg(kwargs, False, "display_derivatives"))
                self.display_phases = bool(self.get_kwarg(kwargs, False, "display_phases"))
                self.display_stats_in_lbl = bool(self.get_kwarg(kwargs, True, "display_stats_in_lbl"))

                self.statistics = Statistics(parent=self)

                pass # end of with
            pass # end of with
        pass # end of __init__
Example #8
0
class Specimen(DataModel, Storable):
    # MODEL INTEL:
    class Meta(DataModel.Meta):
        store_id = "Specimen"

        export_filters = xrd_parsers.get_export_file_filters()
        excl_filters = exc_parsers.get_import_file_filters()

    _data_object = None
    @property
    def data_object(self):
        self._data_object.goniometer = self.goniometer.data_object
        self._data_object.range_theta = self.__get_range_theta()
        self._data_object.selected_range = self.get_exclusion_selector()
        self._data_object.z_list = self.get_z_list()
        try:
            self._data_object.observed_intensity = np.copy(self.experimental_pattern.data_y)
        except IndexError:
            self._data_object.observed_intensity = np.array([], dtype=float)
        return self._data_object

    def get_z_list(self):
        return list(self.experimental_pattern.z_data)

    project = property(DataModel.parent.fget, DataModel.parent.fset)

    # PROPERTIES:

    #: The sample name
    sample_name = StringProperty(
        default="", text="Sample",
        visible=True, persistent=True, tabular=True,
        signal_name="visuals_changed",
        mix_with=(SignalMixin,)
    )

    #: The sample name
    name = StringProperty(
        default="", text="Name",
        visible=True, persistent=True, tabular=True,
        signal_name="visuals_changed",
        mix_with=(SignalMixin,)
    )


    @StringProperty(
        default="", text="Label",
        visible=False, persistent=False, tabular=True,
        mix_with=(ReadOnlyMixin,)
    )
    def label(self):
        if self.display_stats_in_lbl and (self.project is not None and self.project.layout_mode == "FULL"):
            label = self.sample_name
            label += "\nRp = %.1f%%" % not_none(self.statistics.Rp, 0.0)
            label += "\nRwp = %.1f%%" % not_none(self.statistics.Rwp, 0.0)
            return label
        else:
            return self.sample_name

    display_calculated = BoolProperty(
        default=True, text="Display calculated diffractogram",
        visible=True, persistent=True, tabular=True,
        signal_name="visuals_changed",
        mix_with=(SignalMixin,)
    )

    display_experimental = BoolProperty(
        default=True, text="Display experimental diffractogram",
        visible=True, persistent=True, tabular=True,
        signal_name="visuals_changed",
        mix_with=(SignalMixin,)
    )

    display_vshift = FloatProperty(
        default=0.0, text="Vertical shift of the plot",
        visible=True, persistent=True, tabular=True,
        signal_name="visuals_changed", widget_type="spin",
        mix_with=(SignalMixin,)
    )

    display_vscale = FloatProperty(
        default=0.0, text="Vertical scale of the plot",
        visible=True, persistent=True, tabular=True,
        signal_name="visuals_changed", widget_type="spin",
        mix_with=(SignalMixin,)
    )

    display_phases = BoolProperty(
        default=True, text="Display phases seperately",
        visible=True, persistent=True, tabular=True,
        signal_name="visuals_changed",
        mix_with=(SignalMixin,)
    )

    display_stats_in_lbl = BoolProperty(
        default=True, text="Display Rp in label",
        visible=True, persistent=True, tabular=True,
        signal_name="visuals_changed",
        mix_with=(SignalMixin,)
    )

    display_residuals = BoolProperty(
        default=True, text="Display residual patterns",
        visible=True, persistent=True, tabular=True,
        signal_name="visuals_changed",
        mix_with=(SignalMixin,)
    )

    display_residual_scale = FloatProperty(
        default=1.0, text="Residual pattern scale", minimum=0.0,
        visible=True, persistent=True, tabular=True,
        signal_name="visuals_changed", widget_type="spin",
        mix_with=(SignalMixin,)
    )

    display_derivatives = BoolProperty(
        default=False, text="Display derivative patterns",
        visible=True, persistent=True, tabular=True,
        signal_name="visuals_changed",
        mix_with=(SignalMixin,)
    )

    #: A :class:`~pyxrd.generic.models.lines.CalculatedLine` instance
    calculated_pattern = LabeledProperty(
        default=None, text="Calculated diffractogram",
        visible=True, persistent=True, tabular=True,
        signal_name="data_changed", widget_type="xy_list_view",
        mix_with=(SignalMixin, ObserveMixin,)
    )

    #: A :class:`~pyxrd.generic.models.lines.ExperimentalLine` instance
    experimental_pattern = LabeledProperty(
        default=None, text="Experimental diffractogram",
        visible=True, persistent=True, tabular=True,
        signal_name="data_changed", widget_type="xy_list_view",
        mix_with=(SignalMixin, ObserveMixin,)
    )

    #: A list of 2-theta ranges to exclude for the calculation of the Rp factor
    exclusion_ranges = LabeledProperty(
        default=None, text="Excluded ranges",
        visible=True, persistent=True, tabular=True,
        signal_name="data_changed", widget_type="xy_list_view",
        mix_with=(SignalMixin, ObserveMixin)
    )

    #: A :class:`~pyxrd.goniometer.models.Goniometer` instance
    goniometer = LabeledProperty(
        default=None, text="Goniometer",
        visible=True, persistent=True, tabular=True,
        signal_name="data_changed",
        mix_with=(SignalMixin, ObserveMixin,)
    )

    #: A :class:`~pyxrd.specimen.models.Statistics` instance
    statistics = LabeledProperty(
        default=None, text="Markers",
        visible=False, persistent=False, tabular=True,
    )

    #: A list :class:`~pyxrd.specimen.models.Marker` instances
    markers = ListProperty(
        default=None, text="Markers", data_type=Marker,
        visible=False, persistent=True, tabular=True,
        signal_name="visuals_changed", widget_type="object_list_view",
        mix_with=(SignalMixin,)
    )

    @property
    def max_display_y(self):
        """
         The maximum intensity or z-value (display y axis) 
         of the current profile (both calculated and observed)
        """
        _max = 0.0
        if self.experimental_pattern is not None:
            _max = max(_max, np.max(self.experimental_pattern.max_display_y))
        if self.calculated_pattern is not None:
            _max = max(_max, np.max(self.calculated_pattern.max_display_y))
        return _max

    # ------------------------------------------------------------
    #      Initialisation and other internals
    # ------------------------------------------------------------
    def __init__(self, *args, **kwargs):
        """
            Valid keyword arguments for a Specimen are:
                name: the name of the specimen
                sample_name: the sample name of the specimen
                calculated_pattern: the calculated pattern
                experimental_pattern: the experimental pattern
                exclusion_ranges: the exclusion ranges XYListStore
                goniometer: the goniometer used for recording data
                markers: the specimen's markers
                display_vshift: the patterns vertical shift from its default position
                display_vscale: the patterns vertical scale (default is 1.0)
                display_calculated: whether or not to show the calculated pattern
                display_experimental: whether or not to show the experimental pattern
                display_residuals: whether or not to show the residuals
                display_derivatives: whether or not to show the 1st derivative patterns
                display_phases: whether or not to show the separate phase patterns
                display_stats_in_lbl: whether or not to display the Rp values 
                 in the pattern label
        """

        my_kwargs = self.pop_kwargs(kwargs,
            "data_name", "data_sample", "data_sample_length",
            "data_calculated_pattern", "data_experimental_pattern",
            "calc_color", "calc_lw", "inherit_calc_color", "inherit_calc_lw",
            "exp_color", "exp_lw", "inherit_exp_color", "inherit_exp_lw",
            "project_goniometer", "data_markers", "bg_shift", "abs_scale",
            "exp_cap_value", "sample_length", "absorption", "sample_z_dev",
            *[prop.label for prop in Specimen.Meta.get_local_persistent_properties()]
        )
        super(Specimen, self).__init__(*args, **kwargs)
        kwargs = my_kwargs

        self._data_object = SpecimenData()

        with self.visuals_changed.hold_and_emit():
            with self.data_changed.hold_and_emit():
                self.name = self.get_kwarg(kwargs, "", "name", "data_name")
                sample_name = self.get_kwarg(kwargs, "", "sample_name", "data_sample")
                if isinstance(sample_name, bytes):
                    sample_name = sample_name.decode("utf-8", "ignore")
                self.sample_name = sample_name
                
                calc_pattern_old_kwargs = {}
                for kw in ("calc_color", "calc_lw", "inherit_calc_color", "inherit_calc_lw"):
                    if kw in kwargs:
                        calc_pattern_old_kwargs[kw.replace("calc_", "")] = kwargs.pop(kw)
                self.calculated_pattern = self.parse_init_arg(
                    self.get_kwarg(kwargs, None, "calculated_pattern", "data_calculated_pattern"),
                    CalculatedLine,
                    child=True, default_is_class=True,
                    label="Calculated Profile",
                    parent=self,
                    **calc_pattern_old_kwargs
                )

                exp_pattern_old_kwargs = {}
                for kw in ("exp_color", "exp_lw", "inherit_exp_color", "inherit_exp_lw"):
                    if kw in kwargs:
                        exp_pattern_old_kwargs[kw.replace("exp_", "")] = kwargs.pop(kw)
                self.experimental_pattern = self.parse_init_arg(
                    self.get_kwarg(kwargs, None, "experimental_pattern", "data_experimental_pattern"),
                    ExperimentalLine,
                    child=True, default_is_class=True,
                    label="Experimental Profile",
                    parent=self,
                    **exp_pattern_old_kwargs
                )

                self.exclusion_ranges = PyXRDLine(data=self.get_kwarg(kwargs, None, "exclusion_ranges"), parent=self)
                
                # Extract old kwargs if they are there:
                gonio_kwargs = {}
                sample_length = self.get_kwarg(kwargs, None, "sample_length", "data_sample_length")
                if sample_length is not None:
                    gonio_kwargs["sample_length"] = float(sample_length)
                absorption = self.get_kwarg(kwargs, None, "absorption")
                if absorption is not None: # assuming a surface density of at least 20 mg/cm²:
                    gonio_kwargs["absorption"] = float(absorption) / 0.02
                    
                # Initialize goniometer (with optional old kwargs):
                self.goniometer = self.parse_init_arg(
                    self.get_kwarg(kwargs, None, "goniometer", "project_goniometer"),
                    Goniometer, child=True, default_is_class=True,
                    parent=self, **gonio_kwargs
                )

                self.markers = self.get_list(kwargs, None, "markers", "data_markers", parent=self)
                for marker in self.markers:
                    self.observe_model(marker)
                self._specimens_observer = ListObserver(
                    self.on_marker_inserted,
                    self.on_marker_removed,
                    prop_name="markers",
                    model=self
                )

                self.display_vshift = float(self.get_kwarg(kwargs, 0.0, "display_vshift"))
                self.display_vscale = float(self.get_kwarg(kwargs, 1.0, "display_vscale"))
                self.display_calculated = bool(self.get_kwarg(kwargs, True, "display_calculated"))
                self.display_experimental = bool(self.get_kwarg(kwargs, True, "display_experimental"))
                self.display_residuals = bool(self.get_kwarg(kwargs, True, "display_residuals"))
                self.display_residual_scale = float(self.get_kwarg(kwargs, 1.0, "display_residual_scale"))
                self.display_derivatives = bool(self.get_kwarg(kwargs, False, "display_derivatives"))
                self.display_phases = bool(self.get_kwarg(kwargs, False, "display_phases"))
                self.display_stats_in_lbl = bool(self.get_kwarg(kwargs, True, "display_stats_in_lbl"))

                self.statistics = Statistics(parent=self)

                pass # end of with
            pass # end of with
        pass # end of __init__

    def __str__(self):
        return "<Specimen %s(%s)>" % (self.name, repr(self))

    # ------------------------------------------------------------
    #      Notifications of observable properties
    # ------------------------------------------------------------
    @DataModel.observe("data_changed", signal=True)
    def notify_data_changed(self, model, prop_name, info):
        if model == self.calculated_pattern:
            self.visuals_changed.emit() # don't propagate this as data_changed
        else:
            self.data_changed.emit() # propagate signal

    @DataModel.observe("visuals_changed", signal=True)
    def notify_visuals_changed(self, model, prop_name, info):
        self.visuals_changed.emit() # propagate signal

    def on_marker_removed(self, item):
        with self.visuals_changed.hold_and_emit():
            self.relieve_model(item)
            item.parent = None

    def on_marker_inserted(self, item):
        with self.visuals_changed.hold_and_emit():
            self.observe_model(item)
            item.parent = self

    # ------------------------------------------------------------
    #      Input/Output stuff
    # ------------------------------------------------------------
    @staticmethod
    def from_experimental_data(filename, parent, parser=xrd_parsers._group_parser, load_as_insitu=False):
        """
            Returns a list of new :class:`~.specimen.models.Specimen`'s loaded
            from `filename`, setting their parent to `parent` using the given
            parser. If the load_as_insitu flag is set to true, 
        """
        specimens = list()
        xrdfiles = parser.parse(filename)
        if len(xrdfiles):
            if getattr(xrdfiles[0], "relative_humidity_data", None) is not None: # we have relative humidity data
                specimen = None
                
                # Setup list variables:
                x_data = None
                y_datas = []
                rh_datas = []
                
                for xrdfile in xrdfiles:
                    # Get data we need:
                    name, sample, xy_data, rh_data = (
                        xrdfile.filename, xrdfile.name, 
                        xrdfile.data, xrdfile.relative_humidity_data
                    )
                    # Transform into numpy array for column selection
                    xy_data = np.array(xy_data)
                    rh_data = np.array(rh_data)
                    if specimen is None:
                        specimen = Specimen(parent=parent, name=name, sample_name=sample)
                        specimen.goniometer.reset_from_file(xrdfile.create_gon_file())
                        # Extract the 2-theta positions once:
                        x_data = np.copy(xy_data[:,0])
                    
                    # Add a new sub-pattern:
                    y_datas.append(np.copy(xy_data[:,1]))
                    
                    # Store the average RH for this pattern:
                    rh_datas.append(np.average(rh_data))
                    
                specimen.experimental_pattern.load_data_from_generator(zip(x_data, np.asanyarray(y_datas).transpose()), clear=True)
                specimen.experimental_pattern.y_names = ["%.1f" % f for f in rh_datas]
                specimen.experimental_pattern.z_data = rh_datas
                specimens.append(specimen)    
            else: # regular (might be multi-pattern) file
                for xrdfile in xrdfiles:
                    name, sample, generator = xrdfile.filename, xrdfile.name, xrdfile.data
                    specimen = Specimen(parent=parent, name=name, sample_name=sample)
                    # TODO FIXME:
                    specimen.experimental_pattern.load_data_from_generator(generator, clear=True)
                    specimen.goniometer.reset_from_file(xrdfile.create_gon_file())
                    specimens.append(specimen)
        return specimens

    def json_properties(self):
        props = Storable.json_properties(self)
        props["exclusion_ranges"] = self.exclusion_ranges._serialize_data()
        return props

    def get_export_meta_data(self):
        """ Returns a dictionary with common meta-data used in export functions
            for experimental or calculated data """
        return dict(
            sample=self.label + " " + self.sample_name,
            wavelength=self.goniometer.wavelength,
            radius=self.goniometer.radius,
            divergence=self.goniometer.divergence,
            soller1=self.goniometer.soller1,
            soller2=self.goniometer.soller2,
        )


    # ------------------------------------------------------------
    #      Methods & Functions
    # ------------------------------------------------------------
    def clear_markers(self):
        with self.visuals_changed.hold():
            for marker in list(self.markers)[::-1]:
                self.markers.remove(marker)

    def auto_add_peaks(self, tmodel):
        """
        Automagically add peak markers
        
        *tmodel* a :class:`~specimen.models.ThresholdSelector` model
        """
        threshold = tmodel.sel_threshold
        base = 1 if (tmodel.pattern == "exp") else 2
        data_x, data_y = tmodel.get_xy()
        maxtab, mintab = peakdetect(data_y, data_x, 5, threshold) # @UnusedVariable

        mpositions = [marker.position for marker in self.markers]

        with self.visuals_changed.hold():
            i = 1
            for x, y in maxtab: # @UnusedVariable
                if not x in mpositions:
                    nm = self.goniometer.get_nm_from_2t(x) if x != 0 else 0
                    new_marker = Marker(label="%%.%df" % (3 + min(int(log(nm, 10)), 0)) % nm, parent=self, position=x, base=base)
                    self.markers.append(new_marker)
                i += 1

    def get_exclusion_selector(self):
        """
        Get the numpy selector array for non-excluded data
        
        :rtype: a numpy ndarray
        """
        x = self.__get_range_theta() * 360.0 / pi # convert to degrees
        selector = np.ones(x.shape, dtype=bool)
        data = np.sort(np.asarray(self.exclusion_ranges.get_xy_data()), axis=0)
        for x0, x1 in zip(*data):
            new_selector = ((x < x0) | (x > x1))
            selector = selector & new_selector
        return selector

    def get_exclusion_xy(self):
        """
        Get an numpy array containing only non-excluded data X and Y data
                
        :rtype: a tuple containing 4 numpy ndarray's: the experimental X and Y
        data and the calculated X and Y data
        """
        ex, ey = self.experimental_pattern.get_xy_data()
        cx, cy = self.calculated_pattern.get_xy_data()
        selector = self.get_exclusion_selector(ex)
        return ex[selector], ey[selector], cx[selector], cy[selector]

    # ------------------------------------------------------------
    #      Draggable mix-in hook:
    # ------------------------------------------------------------
    def on_pattern_dragged(self, delta_y, button=1):
        if button == 1:
            self.display_vshift += delta_y
        elif button == 3:
            self.display_vscale += delta_y
        elif button == 2:
            self.project.display_plot_offset += delta_y
        pass

    def update_visuals(self, phases):
        """
            Update visual representation of phase patterns (if any)
        """
        if phases is not None:
            self.calculated_pattern.y_names = [
               phase.name if phase is not None else "" for phase in phases
            ]
            self.calculated_pattern.phase_colors = [
                phase.display_color if phase is not None else "#FF00FF" for phase in phases
            ]

    # ------------------------------------------------------------
    #      Intensity calculations:
    # ------------------------------------------------------------
    def update_pattern(self, total_intensity, phase_intensities, phases):
        """
        Update calculated patterns using the provided total and phase intensities
        """
        if len(phases) == 0:
            self.calculated_pattern.clear()
        else:
            maxZ = len(self.get_z_list())
            new_data = np.zeros((phase_intensities.shape[-1], maxZ + maxZ*len(phases))) 
            for z_index in range(maxZ):   
                # Set the total intensity for this z_index:
                new_data[:, z_index] = total_intensity[z_index]
                # Calculate phase intensity offsets:
                phase_start_index = maxZ + z_index * len(phases)
                phase_end_index = phase_start_index + len(phases)
                # Set phase intensities for this z_index:                
                new_data[:,phase_start_index:phase_end_index] = phase_intensities[:,z_index,:].transpose()
                # Store in pattern:
                self.calculated_pattern.set_data(
                    self.__get_range_theta() * 360. / pi,
                    new_data
                )
            self.update_visuals(phases)
        if settings.GUI_MODE:
            self.statistics.update_statistics(derived=self.display_derivatives)

    def convert_to_fixed(self):
        """
        Converts the experimental data from ADS to fixed slits in-place 
        (disregards the `has_ads` flag in the goniometer, but uses the settings
        otherwise) 
        """
        correction = self.goniometer.get_ADS_to_fixed_correction(self.__get_range_theta())
        self.experimental_pattern.apply_correction(correction)

    def convert_to_ads(self):
        """
        Converts the experimental data from fixed slits to ADS in-place 
        (disregards the `has_ads` flag in the goniometer, but uses the settings
        otherwise) 
        """
        correction = 1.0 / self.goniometer.get_ADS_to_fixed_correction(self.__get_range_theta())
        self.experimental_pattern.apply_correction(correction)

    def __get_range_theta(self):
        if len(self.experimental_pattern) <= 1:
            return self.goniometer.get_default_theta_range()
        else:
            return np.radians(self.experimental_pattern.data_x * 0.5)

    def __repr__(self):
        return "Specimen(name='%s')" % self.name

    pass # end of class