<commit_before>import json
from json import JSONEncoder
import numpy as np
from pymicro.xray.detectors import Detector2d, RegArrayDetector2d
from pymicro.crystal.lattice import Lattice, Symmetry
from pymicro.crystal.microstructure import Microstructure, Grain, Orientation


class ForwardSimulation:
    """Class to represent a Forward Simulation."""

    def __init__(self, sim_type, verbose=False):
        self.sim_type = sim_type
        self.verbose = verbose
        self.exp = Experiment()

    def set_experiment(self, experiment):
        """Attach an X-ray experiment to this simulation."""
        self.exp = experiment


class XraySource:
    """Class to represent a X-ray source."""

    def __init__(self, position=None):
        self.set_position(position)
        self._min_energy = None
        self._max_energy = None

    def set_position(self, position):
        if position is None:
            position = (0., 0., 0.)
        self.position = np.array(position)

    @property
    def min_energy(self):
        return self._min_energy

    @property
    def max_energy(self):
        return self._max_energy

    def set_min_energy(self, min_energy):
        self._min_energy = min_energy

    def set_max_energy(self, max_energy):
        self._max_energy = max_energy

    def set_energy(self, energy):
        """Set the energy (monochromatic case)."""
        self.set_min_energy(energy)
        self.set_max_energy(energy)

    def set_energy_range(self, min_energy, max_energy):
        if min_energy < 0:
            print('specified min energy must be positive, using 0 keV')
            min_energy = 0.
        if max_energy <= min_energy:
            print('specified max energy must be larger than min energy, using %.1f' % min_energy)
        self.set_min_energy(min_energy)
        self.set_max_energy(max_energy)


class SlitsGeometry:
    """Class to represent the geometry of a 4 blades slits."""

    def __init__(self, position=None):
        self.set_position(position)  # central position of the aperture (On Xu direction)
        self.hgap = None  # horizontal opening
        self.vgap = None  # vertical opening

    def set_position(self, position):
        if position is None:
            position = (0., 0., 0.)
        self.position = np.array(position)


class ObjectGeometry:
    """Class to represent any object geometry.
    
    The geometry may have multiple form, including just a point, a regular 3D array or it may be described by a CAD 
    file using the STL format. The array represents the material microstructure using the grain ids and should be set 
    in accordance with an associated  Microstructure instance. In this case, zero represent a non crystalline region.
    """

    def __init__(self, geo_type='point', origin=None):
        self.set_type(geo_type)
        self.set_origin(origin)
        # the positions are initially set at the origin, this is a lazy behaviour as discretizing the volume can be expensive
        self.positions = np.array(self.origin)
        self.array = np.ones((1, 1, 1), dtype=np.uint8)  # unique label set to 1
        self.size = np.array([0., 0., 0.])

    def set_type(self, geo_type):
        assert (geo_type in ['point', 'array', 'cad']) is True
        self.geo_type = geo_type

    def set_origin(self, origin):
        if origin is None:
            origin = (0., 0., 0.)
        self.origin = np.array(origin)

    def get_bounding_box(self):
        if self.geo_type in ['point', 'array']:
            return self.origin - self.size / 2, self.origin + self.size / 2
        elif self.geo_type == 'cad':
            bounds = self.cad.GetBounds()
            return (bounds[0], bounds[2], bounds[4]), (bounds[1], bounds[3], bounds[5])

    def get_positions(self):
        return self.positions

    def discretize_geometry(self):
        if self.geo_type == 'point':
            self.positions = np.array(self.origin)
        elif self.geo_type == 'array':
            vx, vy, vz = self.array.shape  # number of voxels
            x_sample = np.linspace(self.get_bounding_box()[0][0], self.get_bounding_box()[1][0], vx)  # mm
            y_sample = np.linspace(self.get_bounding_box()[0][1], self.get_bounding_box()[1][1], vy)  # mm
            z_sample = np.linspace(self.get_bounding_box()[0][2], self.get_bounding_box()[1][2], vz)  # mm
            xx, yy, zz = np.meshgrid(x_sample, y_sample, z_sample, indexing='ij')
            self.positions = np.empty((vx, vy, vz, 3), dtype=float)
            self.positions[:, :, :, 0] = xx
            self.positions[:, :, :, 1] = yy
            self.positions[:, :, :, 2] = zz
        elif self.geo_type == 'cad':
            print('discretizing CAD geometry is not yet supported')
            self.positions = np.array(self.origin)


class Sample:
    """Class to describe a material sample.
    
    A sample is made by a given material (that may have multiple phases), has a name and a position in the experimental 
    local frame. A sample also has a geometry (just a point by default), that may be used to discretize the volume 
    in space or display it in 3D.
    <commit_msg>Sample Geometry now supports point, array and cad properly<commit_after>import json
from json import JSONEncoder
import numpy as np
from pymicro.xray.detectors import Detector2d, RegArrayDetector2d
from pymicro.crystal.lattice import Lattice, Symmetry
from pymicro.crystal.microstructure import Microstructure, Grain, Orientation


class ForwardSimulation:
    """Class to represent a Forward Simulation."""

    def __init__(self, sim_type, verbose=False):
        self.sim_type = sim_type
        self.verbose = verbose
        self.exp = Experiment()

    def set_experiment(self, experiment):
        """Attach an X-ray experiment to this simulation."""
        self.exp = experiment


class XraySource:
    """Class to represent a X-ray source."""

    def __init__(self, position=None):
        self.set_position(position)
        self._min_energy = None
        self._max_energy = None

    def set_position(self, position):
        if position is None:
            position = (0., 0., 0.)
        self.position = np.array(position)

    @property
    def min_energy(self):
        return self._min_energy

    @property
    def max_energy(self):
        return self._max_energy

    def set_min_energy(self, min_energy):
        self._min_energy = min_energy

    def set_max_energy(self, max_energy):
        self._max_energy = max_energy

    def set_energy(self, energy):
        """Set the energy (monochromatic case)."""
        self.set_min_energy(energy)
        self.set_max_energy(energy)

    def set_energy_range(self, min_energy, max_energy):
        if min_energy < 0:
            print('specified min energy must be positive, using 0 keV')
            min_energy = 0.
        if max_energy <= min_energy:
            print('specified max energy must be larger than min energy, using %.1f' % min_energy)
        self.set_min_energy(min_energy)
        self.set_max_energy(max_energy)


class SlitsGeometry:
    """Class to represent the geometry of a 4 blades slits."""

    def __init__(self, position=None):
        self.set_position(position)  # central position of the aperture (On Xu direction)
        self.hgap = None  # horizontal opening
        self.vgap = None  # vertical opening

    def set_position(self, position):
        if position is None:
            position = (0., 0., 0.)
        self.position = np.array(position)


class ObjectGeometry:
    """Class to represent any object geometry.
    
    The geometry may have multiple form, including just a point, a regular 3D array or it may be described by a CAD 
    file using the STL format. The array represents the material microstructure using the grain ids and should be set 
    in accordance with an associated  Microstructure instance. In this case, zero represent a non crystalline region.
    """

    def __init__(self, geo_type='point', origin=None):
        self.set_type(geo_type)
        self.set_origin(origin)
        # the positions are initially set at the origin, this is a lazy behaviour as discretizing the volume can be expensive
        self.positions = np.array(self.origin)
        self.array = np.ones((1, 1, 1), dtype=np.uint8)  # unique label set to 1
        self.size = np.array([0., 0., 0.])  # mm units
        self.cad = None

    def set_type(self, geo_type):
        assert (geo_type in ['point', 'array', 'cad']) is True
        self.geo_type = geo_type

    def set_array(self, grain_map, voxel_size):
        self.array = grain_map
        self.size = np.array(grain_map.shape) * voxel_size
        print('size set to {}'.format(self.size))

    def set_origin(self, origin):
        if origin is None:
            origin = (0., 0., 0.)
        self.origin = np.array(origin)

    def get_bounding_box(self):
        if self.geo_type in ['point', 'array']:
            return self.origin - self.size / 2, self.origin + self.size / 2
        elif self.geo_type == 'cad':
            bounds = self.cad.GetBounds()
            return (bounds[0], bounds[2], bounds[4]), (bounds[1], bounds[3], bounds[5])

    def get_positions(self):
        """Return an array of the positions within this sample in world coordinates."""
        return self.positions

    def discretize_geometry(self, grain_id=None):
        """Compute the positions of material points inside the sample.

        A array of size (n_vox, 3) is returned where n_vox is the number of positions. The 3 values of each position
        contain the (x, y, z) coordinates in mm unit. If a grain id is specified, only the positions within this grain
        are returned.

        This is useful in forward simulation where we need to access all the locations within the sample. Three cases
        are available:

         * point Laue diffraction: uses sample origin, grains center and orientation
         * cad Laue diffraction: uses origin, cad file geometry and grains[0] orientation (assumes only one grain)
         * grain map Laue diffraction: uses origin, array, size, and grains orientations.

         to set the cad geometry must be the path to the STL file.
        """
        if self.geo_type == 'point':
            self.positions = np.array(self.origin)
        elif self.geo_type == 'array':
            vx, vy, vz = self.array.shape  # number of voxels
            print(vx, vy, vz)
            bb = self.get_bounding_box()
            print('bounding box is {}'.format(bb))
            x_sample = np.linspace(bb[0][0], bb[1][0], vx)  # mm
            y_sample = np.linspace(bb[0][1], bb[1][1], vy)  # mm
            z_sample = np.linspace(bb[0][2], bb[1][2], vz)  # mm
            if grain_id:
                # filter by the given grain id
                ndx_x, ndx_y, ndx_z = np.where(self.array == grain_id)
                print('found %d voxels in grain %d' % (len(ndx_x), grain_id))
                self.positions = np.c_[x_sample[ndx_x], y_sample[ndx_y], z_sample[ndx_z]]
            else:
                xx, yy, zz = np.meshgrid(x_sample, y_sample, z_sample, indexing='ij')
                all_positions = np.empty((vx, vy, vz, 3), dtype=float)
                all_positions[:, :, :, 0] = xx
                all_positions[:, :, :, 1] = yy
                all_positions[:, :, :, 2] = zz
                self.positions = all_positions.reshape(-1, all_positions.shape[-1])
        elif self.geo_type == 'cad':
            if self.cad is None:
                print('you must set the cad attribute (path to the STL file in mm units) for this geometry')
                return
            from pymicro.view.vtk_utils import is_in_array
            is_in, xyz = is_in_array(self.cad, step=0.2, origin=self.origin)
            self.positions = xyz[np.where(is_in.ravel())]


class Sample:
    """Class to describe a material sample.
    
    A sample is made by a given material (that may have multiple phases), has a name and a position in the experimental 
    local frame. A sample also has a geometry (just a point by default), that may be used to discretize the volume 
    in space or display it in 3D.
    
{'fraction_non_alphanumeric': 0.09861122524447366, 'fraction_numerical': 0.008792834250965569, 'mean_word_length': 2.516324761629587, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1045283', 'n_tokens_mistral': 3718, 'n_tokens_neox': 3499, 'n_words': 1187}