/** * Instantiate a new ProbeOutputImageDecoder * @param output The source of probedata from which to generate image data. */ public BModeOutputImageDecoder(IProbeOutput output) { UltrasoundDebug.Assert(null != output, "Null probe output used in constructor", this); drawColor = Color.white; probeOutput = output; imageEffects = new List <IImagePostProcessor>(); }
/// Use this for initialization void Start() { #if UNITY_EDITOR // Some input validation UltrasoundDebug.Assert(NumberOfScanlines > 0, "Number of scanlines should be a positive integer", this, true); UltrasoundDebug.Assert(PointsPerScanline > 0, "Points per scanline should be a positive integer", this, true); UltrasoundDebug.Assert(ArcSizeInDegrees >= 0 && ArcSizeInDegrees <= 180f, "Arc size should be between 0 and 180 degrees.", this, true); UltrasoundDebug.Assert(MinDistance > 0, "Min distance should be greater than 0", this, true); UltrasoundDebug.Assert(MaxDistance > 0, "Max distance should be greater than 0", this, true); UltrasoundDebug.Assert(MinDistance < MaxDistance, "Min distance should be smaller than max distance", this, true); #endif output = new HorayProbeOutput(this.gameObject); dataSource = new HorayProbe(this.gameObject, output); }
/** * Instantiate a new instance of UltrasoundScanData. * @param config The configuration settings associated with the probe object. */ public UltrasoundScanData(UltrasoundProbeConfiguration config) { #if UNITY_EDITOR UltrasoundDebug.Assert(null != config, "Null config used in constructor", this); #endif this.probeConfig = config; scanlines = new List <UltrasoundScanline>(); }
/** * Add an UltrasoundScanline to this UltrasoundScanData. * * @param s The UltrasoundScanline to add. */ public void AddScanline(UltrasoundScanline s) { #if UNITY_EDITOR UltrasoundDebug.Assert(null != s, "A null scanline was added to this UltrasoundScanData", this); #endif scanlines.Add(s); }
/** * Add an UltrasoundPoint to this scanline. * * @param p The UltrasoundPoint to add. * @throw ArgumentNullException */ public void AddUltrasoundPoint(UltrasoundPoint p) { #if UNITY_EDITOR UltrasoundDebug.Assert(null != p, "A null UltrasoundPoint was added to a Scanline", this); #endif points.Add(p); }
/** * Set the gain of this probe. * * @param gain A non-negative float value. */ public void SetGain(float gain) { #if UNITY_EDITOR UltrasoundDebug.Assert(gain > 0f, string.Format("Tried to set gain to {0}", gain), this); #endif this.gain = gain; }
/** * Sets the number of points to check per scanline. * @param count A positive integer value. */ public void SetPointsPerScanline(int count) { #if UNITY_EDITOR UltrasoundDebug.Assert(count > 0, string.Format("Tried to set points per scanline to {0}", count), this, false); #endif pointsPerScanline = count; }
/** * Set the number of scanlines to scan. * @param count A positive integer value. */ public void SetNumberOfScanlines(int count) { #if UNITY_EDITOR UltrasoundDebug.Assert(count > 0, string.Format("Tried to set scanline count to {0}", count), this, false); #endif numberOfScanlines = Mathf.Clamp(count, 0, int.MaxValue); }
/** * Sets the size of the scanning arc, measured in degrees. * @param degrees A float value between 0 and 180 (inclusive). */ public void SetArcSizeInDegrees(float degrees) { #if UNITY_EDITOR UltrasoundDebug.Assert(degrees >= 0f && degrees <= 180f, string.Format("Tried to set degrees to {0}", degrees), this, false); #endif arcSizeInDegrees = Mathf.Clamp(degrees, 0f, 180f); }
/// Use this for initialization void Start() { skin = GameObject.FindGameObjectWithTag("Skin"); UltrasoundDebug.Assert(null != skin, "Could not find Skin object. Did you forget to tag it?", this); UltrasoundDebug.Assert(null != skin.collider, "Skin object does not have a collider.", this); dragMode = false; }
/** * Create a new UltrasoundPoint. * @param worldSpaceLocation The location of the point in 3D world space. * @param projectedLocation Within a scanning plane, the 2D position of the point. */ public UltrasoundPoint(Vector3 worldSpaceLocation, Vector2 projectedLocation) { #if UNITY_EDITOR UltrasoundDebug.Assert(projectedLocation.y > 0f, "y coordinate of projectedLocation should be positive.", this); #endif this.worldSpaceLocation = worldSpaceLocation; this.projectedLocation = projectedLocation; brightness = 0f; }
/** * Sets the maximum scanning distance of the probe. * @param max Clamped within the (exclusive) interval 0 to Positive Infinity. */ public void SetMaxScanDistance(float max) { maxDistance = Mathf.Clamp(max, float.Epsilon, float.MaxValue); #if UNITY_EDITOR UltrasoundDebug.Assert(maxDistance > minDistance, string.Format("SetMaxScanDistance:" + "Max distance ({0}) should be greater than min distance ({1})!", maxDistance, minDistance), this); #endif }
/** * Add a collection of UltrasoundScanline%s to this scanline. * * @param scanlines The UltrasoundScanline%s to add. */ public void AddScanlines(ICollection <UltrasoundScanline> scanlines) { #if UNITY_EDITOR UltrasoundDebug.Assert(null != scanlines, "A null collection of scanlines was added to this UltrasoundScanData", this); #endif foreach (UltrasoundScanline s in scanlines) { AddScanline(s); } }
/** * Add a collection of UltrasoundPoint%s to this scanline. * * @param points The UltrasoundPoint%s to add. * @throw ArgumentNullException */ public void AddUltrasoundPoints(ICollection <UltrasoundPoint> points) { #if UNITY_EDITOR UltrasoundDebug.Assert(null != points, "A null collection of points was added to a Scanline", this); #endif foreach (UltrasoundPoint p in points) { AddUltrasoundPoint(p); } }
/** * Sets up a standard HORAY configuration: * * probe -> HorayProbeOutput -> BModeOutputImageDecoder (+blur) -> TextureSource */ public static ITextureSource BuildStandardHORAYConfig() { GameObject probe = GameObject.FindGameObjectWithTag("Probe"); UltrasoundDebug.Assert(null != probe, "No object with Probe tag in scene.", new DisplayTexturePipelineFactory()); IProbeOutput horayOutput = new HorayProbeOutput(probe); IImageSource bmodeImageDecoder = new BModeOutputImageDecoder(horayOutput); bmodeImageDecoder.AddPostProcessingEffect(new GrayscaleGaussianBlur()); return(new TextureSource(bmodeImageDecoder)); }
/** * Removes organs that are visible, but haven't been seen recently enough. * (i.e. the time that the organ was last hit by a raycast is more than EXPIRATION_TIME_IN_SECONDS ago) */ private void RemoveExpiredObjects() { /* We store the keys and organs to remove in temporary lists. This is to avoid bugs associated * with editing the items in a Dictionary while iterating over it in a foreach loop. */ IList <float> keysToRemove = new List <float>(); IList <GameObject> organsToRemove = new List <GameObject>(); foreach (float timeLastSeen in currentVisibleOrgans.Keys) { float timeElapsed = Time.time - timeLastSeen; if (timeElapsed >= EXPIRATION_TIME_IN_SECONDS) { GameObject organToRemove = null; currentVisibleOrgans.TryGetValue(timeLastSeen, out organToRemove); #if UNITY_EDITOR UltrasoundDebug.Assert(null != organToRemove, string.Format("Couldn't find organ added at time {0}", timeLastSeen), this); #endif keysToRemove.Add(timeLastSeen); organsToRemove.Add(organToRemove); } else { // Remember that the SortedDictionary implementation will store the oldest objects first. // As soon as we reach an object that isn't expired, we know that all the rest won't be expired either. break; } } #if UNITY_EDITOR UltrasoundDebug.Assert(organsToRemove.Count == keysToRemove.Count, string.Format("{0} organs marked for removal, {1} keys marked for removal", organsToRemove.Count, keysToRemove.Count), this); #endif for (int i = 0; i < organsToRemove.Count; ++i) { currentVisibleOrgans.Remove(keysToRemove[i]); reverseMapLookup.Remove(organsToRemove[i]); } #if UNITY_EDITOR UltrasoundDebug.Assert(currentVisibleOrgans.Count == reverseMapLookup.Count, string.Format("currentVisibleOrgans.Count = {0}, reverseMapLookup.Count = {1}", currentVisibleOrgans.Count, reverseMapLookup.Count), this); #endif }
/** * Instantiate a new UltrasoundProbeConfiguration with a UnityEngine.Transform. * Other configuration values use the defaults: * * - minDistance: float.Epsilon (1.40E-45) * - maxDistance: 10.0 * - arcSizeInDegrees: 75 * - numberOfScanlines: 40 * - pointsPerScanline: 40 * - gain: 1.0f * * @param probeTransform The UnityEngine.Transform of the probe GameObject. */ public UltrasoundProbeConfiguration(Transform probeTransform) { UltrasoundDebug.Assert(null != probeTransform, "Null transform passed to UltrasoundProbeConfiguration constructor.", this); this.SetRotation(probeTransform.rotation); this.SetPosition(probeTransform.position); this.minDistance = float.Epsilon; this.maxDistance = 10f; this.arcSizeInDegrees = 75; this.numberOfScanlines = 40; this.pointsPerScanline = 40; this.gain = 1.0f; }
/** * Copy constructor to instantiate a new UltrasoundProbeConfiguration from another. * * @param config The other UltrasoundProbeConfiguration object. * @throw ArgumentNullException */ public UltrasoundProbeConfiguration(UltrasoundProbeConfiguration config) { UltrasoundDebug.Assert(null != config, "Null UltrasoundProbeConfiguration used for copy constructor.", this); this.SetPosition(config.GetPosition()); this.SetRotation(config.GetRotation()); this.maxDistance = config.GetMaxScanDistance(); this.minDistance = config.GetMinScanDistance(); this.arcSizeInDegrees = config.GetArcSizeInDegrees(); this.pointsPerScanline = config.GetPointsPerScanline(); this.numberOfScanlines = config.GetNumberOfScanlines(); this.gain = config.GetGain(); }
/** * Populates an UltrasoundScanData with scan data for this frame. * @param data The object into which to put the scan data. */ public void PopulateData(ref UltrasoundScanData data) { OnionLogger.globalLog.PushInfoLayer("HORAYProbe populating data"); #if UNITY_EDITOR UltrasoundDebug.Assert(null != data, "Null data object passed to HorayProbe's PopulateData method.", this); #endif EstablishScanningPlane(ref data); IList <GameObject> culledOrganList = culler.HitableOrgansOnScanlines(data.GetScanlines(), data.GetProbeConfig()); ScanPointsForOrgans(ref data, culledOrganList); OnionLogger.globalLog.PopInfoLayer(); }
/// Use this for initialization void Start() { initialPosition = this.transform.position; initialRotation = this.transform.rotation; skin = GameObject.FindGameObjectWithTag("Skin"); UltrasoundDebug.Assert(null != bodyPivotPoint, "Body object not set!", this); UltrasoundDebug.Assert(null != skin, "Could not find Skin object. Did you forget to tag it?", this); UltrasoundDebug.Assert(null != skin.collider, "The skin object does not seem to have a collider.", this); }
/** * Instantiate a new HorayProbeOutput. * @param gameObject The probe from which data will be transmitted. * @throw ArgumentException If the probe object does not have the correct components. */ public HorayProbeOutput(GameObject gameObject) { UltrasoundDebug.Assert(null != gameObject, "Null GameObject passed to HorayProbeOutput constructor.", this); if (null == gameObject.GetComponent <HorayBehavior>()) { string str = "The probe object used to instantiate this output " + "does not have the required HorayBehavior script."; Debug.LogError(str); } this.probeGameObject = gameObject; probe = new HorayProbe(probeGameObject, this); }
/** * Instantiate a new HorayOrganCuller. */ public HorayOrganCuller() { currentVisibleOrgans = new SortedDictionary <float, GameObject>(); reverseMapLookup = new Dictionary <GameObject, float>(); allOrgansInScene = new List <GameObject>(); object[] allGameObjectsInScene = GameObject.FindObjectsOfType <GameObject>(); foreach (Object anObject in allGameObjectsInScene) { GameObject gameObject = (GameObject)anObject; if (null != gameObject.GetComponent <HorayMaterialProperties>()) { allOrgansInScene.Add(gameObject); } } UltrasoundDebug.Assert(allOrgansInScene.Count > 0, "No valid HORAY organs were found.", this); }
/** * Instantiate a new HorayProbe. * @param probe The GameObject that represents the scanning array. * @param output An IProbeOutput to pass information to. */ public HorayProbe(GameObject probe, IProbeOutput output) { #if UNITY_EDITOR UltrasoundDebug.Assert(null != probe, "Null probe GameObject passed to HorayProbe constructor.", this); UltrasoundDebug.Assert(null != output, "Null IProbeOutput passed to HorayProbe constructor.", this); if (null == probe.GetComponent <HorayBehavior>()) { string str = "The probe object used to instantiate this class " + "does not have the required HorayBehavior script."; Debug.LogError(str); } #endif this.probeGameObject = probe; this.output = output; this.culler = new HorayOrganCuller(); }
/// Initialization when the scene is started. This sets up the source of dynamic texture generation for the display. /// Based on the current choice of DisplayModes, the DisplayTexturePipelineFactory will provide the appropriate /// ITextureSource. void Start() { OnionLogger.globalLog.PushInfoLayer("Initializing Display"); texture = new Texture2D(textureWidth, textureHeight, TextureFormat.RGB24, false); this.renderer.material.mainTexture = texture; switch (displayMode) { case (DisplayModes.HORAY): textureSource = DisplayTexturePipelineFactory.BuildStandardHORAYConfig(); break; case (DisplayModes.InvHORAY): textureSource = DisplayTexturePipelineFactory.BuildBlackOnWhiteHORAYConfig(); break; case (DisplayModes.FakeTexture): textureSource = DisplayTexturePipelineFactory.BuildFakeTextureSource(); break; case (DisplayModes.FakeImage): textureSource = DisplayTexturePipelineFactory.BuildWithFakeImageSource(); break; case (DisplayModes.FakeProbeOutput): textureSource = DisplayTexturePipelineFactory.BuildWithFakeBModeProbeOutput(); break; case (DisplayModes.GaussianBlurTest): textureSource = DisplayTexturePipelineFactory.BuildWithGaussianBlurImageSource(); break; default: UltrasoundDebug.Assert(false, "Unknown display mode!", this); break; } OnionLogger.globalLog.PopInfoLayer(); }
/// Checks an individual scanline for valid organs. /// @param scanline The UltrasoundScanline being tested /// @return A list of organs that are on the scanline. private IList <GameObject> ValidOrgansOnScanline(UltrasoundScanline scanline) { IList <UltrasoundPoint> points = scanline.GetPoints(); #if UNITY_EDITOR UltrasoundDebug.Assert(points.Count > 0, "Zero points on scanline", this); #endif Vector3 terminalPoint = points[points.Count - 1].GetWorldSpaceLocation(); Ray ray = new Ray(scanline.origin, terminalPoint - scanline.origin); float raycastDistance = (terminalPoint - scanline.origin).magnitude; RaycastHit[] hits = Physics.RaycastAll(ray, raycastDistance); IList <GameObject> validOrgans = new List <GameObject>(); foreach (RaycastHit hit in hits) { if (allOrgansInScene.Contains(hit.collider.gameObject)) { validOrgans.Add(hit.collider.gameObject); } } return(validOrgans); }
/** * Draw a single UltrasoundPoint into a color buffer. * @param point The UltrasoundPoint data to draw. * @param index The index in the buffer at which to place the pixel. * @param bitmap The bitmap into which to draw the pixel. */ protected virtual void DrawPoint(UltrasoundPoint point, int index, ref ColorBitmap bitmap) { long bufferSize = bitmap.colors.Length; #if UNITY_EDITOR UltrasoundDebug.Assert(index < bufferSize && index >= 0, string.Format("{0} should be in the interval[0, {1})", index, bufferSize), this); UltrasoundDebug.Assert(point.GetBrightness() >= 0f && point.GetBrightness() <= 1f, string.Format("Pixel brightness {0} should be in the interval [0,1]", point.GetBrightness()), this); #endif if (index >= bufferSize || index < 0) { return; } Color pointColor = drawColor; // minimum pixel brightness of 0.1f pointColor *= Mathf.Max(0.1f, point.GetBrightness()); bitmap.colors[index] = pointColor; }
public void AddPostProcessingEffect(IImagePostProcessor effect) { UltrasoundDebug.Assert(null != effect, "Null effect added to ImageSource", this); imageEffects.Add(effect); }
/** * Instantiate a new TextureSource. * @param imageSource The IImageSource providing a bitmap every frame. */ public TextureSource(IImageSource imageSource) { UltrasoundDebug.Assert(null != imageSource, "Null ImageSource used in constructor", this, true); this.imageSource = imageSource; }
/** * Checks a list of scanlines for visible objects. If an object on the scanlines is hit by * a raycast, it is marked as visible (added to the currentVisibleOrgans and reverseMapLookup dictionaries). * * Visible objects are given an expiration time (EXPIRATION_TIME_IN_SECONDS after the present). * If an object that is already known to be visible is detected, its expiration time is reset. * * This method only checks a few scanlines each frame (as defined in the SCANLINES_PER_FRAME constant). * We attempt to check scanlines that are spread apart, rather than checking sequentially, so that new * objects are detected more quickly. For example, if there are 10 scanlines in total, it is more helpful * to check scanlines (0, 3, 6, 9) in a single frame than to check (0, 1, 2, 3). * * @param scanlines The set of all scanlines sent out by this probe. */ private void CheckScanlines(IList <UltrasoundScanline> scanlines) { int scanlineCount = scanlines.Count; #if UNITY_EDITOR UltrasoundDebug.Assert(scanlineCount > 0, "Zero scanlines passed to culler!", this); #endif // Rather than checking scanlines sequentially, we try to check them somewhat uniformly so // that new objects are more likely to be noticed within a few frames. Therefore, we generate // a number smaller than scanlineCount, but one that is NOT a divisor of scanlineCount. int scanlineStepSize = (scanlineCount / (SCANLINES_PER_FRAME + 1)); while (scanlineCount % scanlineStepSize == 0) { scanlineStepSize++; } for (int i = 0; i < SCANLINES_PER_FRAME; ++i) { scanlineIndex = (scanlineIndex + scanlineStepSize) % scanlineCount; IList <GameObject> organsToAdd = (ValidOrgansOnScanline(scanlines[scanlineIndex])); foreach (GameObject organ in organsToAdd) { #if UNITY_EDITOR UltrasoundDebug.Assert(null != organ.GetComponent <HorayMaterialProperties>(), "organ without HORAY properties was added", this); #endif if (!reverseMapLookup.ContainsKey(organ)) { // Store in a temp variable since Time.time may change between assignments below. float keyTime = Time.time; /* On faster machines, two raycasts may be completed before Time.time has been measureably * incremented. In this situation, we can nudge the time forward a little bit to avoid a * key collision. */ while (currentVisibleOrgans.ContainsKey(keyTime)) { keyTime += 0.01f; } currentVisibleOrgans.Add(keyTime, organ); reverseMapLookup.Add(organ, keyTime); } else { float oldTime = float.NegativeInfinity; reverseMapLookup.TryGetValue(organ, out oldTime); #if UNITY_EDITOR UltrasoundDebug.Assert(oldTime > float.NegativeInfinity, string.Format("Organ {0} wasn't in reverse map", organ.name), this); #endif currentVisibleOrgans.Remove(oldTime); reverseMapLookup.Remove(organ); float newTime = Time.time; /* On faster machines, two raycasts may be completed before Time.time has been measureably * incremented. In this situation, we can nudge the time forward a little bit to avoid a * key collision. */ while (currentVisibleOrgans.ContainsKey(newTime)) { newTime += 0.01f; } currentVisibleOrgans.Add(newTime, organ); reverseMapLookup.Add(organ, newTime); } } } }