/// <summary> /// Get a string out of a vector of chars. /// </summary> /// <param name="indices"></param> /// <returns></returns> /// <remarks>Performs a copy due to String .net-framework limitations.</remarks> public string GetString(params int[] indices) { unsafe { if (Shape.dimensions.Length - 1 != indices.Length) { throw new ArgumentOutOfRangeException(nameof(indices), "GetString(int[]) can only accept coordinates that point to a vector of chars."); } Debug.Assert(typecode == NPTypeCode.Char); UnmanagedStorage src = Storage.GetData(indices); Debug.Assert(src.Shape.NDim == 1); if (!Shape.IsContiguous || Shape.ModifiedStrides) { //this works faster than cloning. var ret = new string('\0', src.Count); fixed(char *retChars = ret) { var dst = new UnmanagedStorage(new ArraySlice <char>(new UnmanagedMemoryBlock <char>(retChars, ret.Length)), src.Shape.Clean()); MultiIterator.Assign(dst, src); } return(ret); } //new string always performs a copy, there is no need to keep reference to arr's unmanaged storage. return(new string((char *)src.Address, 0, src.Count)); } }
/// <summary> /// Copies values from one array to another, broadcasting as necessary. /// </summary> /// <param name="dst">The array into which values are copied.</param> /// <param name="src">The array from which values are copied.</param> /// <remarks>https://docs.scipy.org/doc/numpy/reference/generated/numpy.copyto.html</remarks> public static void copyto(NDArray dst, NDArray src) //todo! add where argument { if (dst == null) { throw new ArgumentNullException(nameof(dst)); } if (src == null) { throw new ArgumentNullException(nameof(src)); } //try to perform memory copy if (dst.Shape.IsContiguous && src.Shape.IsContiguous && dst.dtype == src.dtype && src.size == dst.size) { unsafe { src.CopyTo(dst.Address); return; } } //perform manual copy with automatic casting MultiIterator.Assign(dst.Storage, src.Storage); }
/// <summary> /// Join a sequence of arrays along an existing axis. /// </summary> /// <param name="axis">The axis along which the arrays will be joined. If axis is None, arrays are flattened before use. Default is 0.</param> /// <param name="arrays">The arrays must have the same shape, except in the dimension corresponding to axis (the first, by default).</param> /// <returns>The concatenated array.</returns> /// <remarks>https://docs.scipy.org/doc/numpy/reference/generated/numpy.concatenate.html</remarks> public static NDArray concatenate(NDArray[] arrays, int axis = 0) { //What we do is we have the axis which is the only dimension that is allowed to be different //We need to perform a check if the dimensions actually match. //After we have the axis ax=1 where shape is (3,ax,3) - ax is the only dimension that can vary. //So if we got input of (3,5,3) and (3,1,3), we create a return shape of (3,6,3). //We perform the assignment by iterating a slice: (:,n,:) on src and dst where dst while n of dst grows as we iterate all arrays. if (arrays == null) { throw new ArgumentNullException(nameof(arrays)); } if (arrays.Length == 0) { throw new ArgumentException("Value cannot be an empty collection.", nameof(arrays)); } if (arrays.Length == 1) { return(arrays[0]); } var first = arrays[0]; var firstShape = (int[])first.shape.Clone(); while (axis < 0) { axis = first.ndim + axis; //translate negative axis } int i, j; int axisSize = 0; //accumulated shape[axis] size for return shape. NPTypeCode retType = first.GetTypeCode; foreach (var src in arrays) { //accumulate the concatenated axis var shape = src.shape; axisSize += shape[axis]; if (ReferenceEquals(src, first)) { continue; } var srcType = src.GetTypeCode; //resolve what the return type should be and should we perform casting. if (first.GetTypeCode != srcType) { if (srcType.CompareTo(retType) == 1) { retType = srcType; } } if (shape.Length != first.ndim) { throw new IncorrectShapeException("all the input arrays must have same number of dimensions."); } //verify the shapes are equal for (j = 0; j < shape.Length; j++) { if (axis == j) { continue; } if (shape[j] != firstShape[j]) { throw new IncorrectShapeException("all the input array dimensions except for the concatenation axis must match exactly."); } } } //prepare return shape firstShape[axis] = axisSize; var retShape = new Shape(firstShape); var dst = new NDArray(retType, retShape); var accessorDst = new Slice[retShape.NDim]; var accessorSrc = new Slice[retShape.NDim]; for (i = 0; i < accessorDst.Length; i++) { accessorSrc[i] = accessorDst[i] = Slice.All; } accessorSrc[axis] = Slice.Index(0); accessorDst[axis] = Slice.Index(0); foreach (var src in arrays) { var len = src.shape[axis]; for (i = 0; i < len; i++) { var writeTo = dst[accessorDst]; var writeFrom = src[accessorSrc]; MultiIterator.Assign(writeTo.Storage, writeFrom.Storage); accessorSrc[axis]++; accessorDst[axis]++; //increment every step } accessorSrc[axis] = Slice.Index(0); //reset src } return(dst); }
/// <summary> /// Converts <see cref="NDArray"/> to a <see cref="Bitmap"/>. /// </summary> /// <param name="nd">The <see cref="NDArray"/> to copy pixels from, <see cref="Shape"/> is ignored completely. If nd.Unsafe.Shape.IsContiguous == false then a copy is made.</param> /// <param name="width">The height of the <see cref="Bitmap"/></param> /// <param name="height">The width of the <see cref="Bitmap"/></param> /// <param name="format">The format of the expected bitmap, Must be matching to given NDArray otherwise unexpected results might occur.</param> /// <returns>A <see cref="Bitmap"/></returns> /// <exception cref="ArgumentException">When nd.size != width*height, which means the ndarray be turned into the given bitmap size.</exception> public static unsafe Bitmap ToBitmap(this NDArray nd, int width, int height, PixelFormat format = PixelFormat.DontCare) { if (nd == null) { throw new ArgumentNullException(nameof(nd)); } //if flat then initialize based on given format if (nd.ndim == 1 && format != PixelFormat.DontCare) { nd = nd.reshape(1, height, width, format.ToBytesPerPixel()); //theres a check internally for size mismatch. } if (nd.ndim != 4) { throw new ArgumentException("ndarray was expected to be of 4-dimensions, (1, bmpData.Height, bmpData.Width, bytesPerPixel)"); } if (nd.shape[0] != 1) { throw new ArgumentException($"ndarray has more than one picture in it ({nd.shape[0]}) based on the first dimension."); } var bbp = nd.shape[3]; //bytes per pixel. if (bbp != extractFormatNumber()) { throw new ArgumentException($"Given PixelFormat: {format} does not match the number of bytes per pixel in the 4th dimension of given ndarray."); } if (bbp * width * height != nd.size) { throw new ArgumentException($"The expected size does not match the size of given ndarray. (expected: {bbp * width * height}, actual: {nd.size})"); } var ret = new Bitmap(width, height, format); var bitdata = ret.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, format); try { var dst = new ArraySlice <byte>(new UnmanagedMemoryBlock <byte>((byte *)bitdata.Scan0, bitdata.Stride * bitdata.Height)); if (nd.Shape.IsContiguous) { nd.CopyTo(dst); } else { MultiIterator.Assign(new UnmanagedStorage(dst, Shape.Vector(bitdata.Stride * bitdata.Height)), nd.Unsafe.Storage); } } finally { ret.UnlockBits(bitdata); } return(ret); int extractFormatNumber() { if (format == PixelFormat.DontCare) { switch (bbp) { case 3: format = PixelFormat.Format24bppRgb; break; case 4: format = PixelFormat.Format32bppArgb; break; case 6: format = PixelFormat.Format48bppRgb; break; case 8: format = PixelFormat.Format64bppArgb; break; } return(bbp); } return(format.ToBytesPerPixel()); } }